├── tests
├── unit
│ └── .gitkeep
├── integration
│ ├── .gitkeep
│ └── components
│ │ ├── ast-plugins-test.js
│ │ ├── test-inline-precompile-test.js
│ │ └── colocation-test.js
├── dummy
│ ├── app
│ │ ├── components
│ │ │ ├── pods-comp
│ │ │ │ └── template.hbs
│ │ │ ├── x-module-name-inlined-component.hbs
│ │ │ └── x-module-name-reversed-component.hbs
│ │ ├── templates
│ │ │ ├── styleguide.hbs
│ │ │ └── application.hbs
│ │ ├── resolver.js
│ │ ├── styles
│ │ │ └── app.css
│ │ ├── router.js
│ │ ├── app.js
│ │ └── index.html
│ ├── public
│ │ └── robots.txt
│ ├── config
│ │ ├── optional-features.json
│ │ ├── targets.js
│ │ ├── ember-cli-update.json
│ │ ├── environment.js
│ │ └── ember-try.js
│ └── lib
│ │ └── module-name-inliner
│ │ ├── package.json
│ │ └── index.js
├── colocation
│ └── app
│ │ └── components
│ │ ├── bar.hbs
│ │ ├── its-native.hbs
│ │ ├── foo.hbs
│ │ ├── baz
│ │ └── index.hbs
│ │ ├── foo
│ │ ├── bar.hbs
│ │ └── baz
│ │ │ └── index.hbs
│ │ ├── bar.js
│ │ └── its-native.js
├── test-helper.js
├── acceptance
│ └── styleguide-test.js
├── index.html
└── helpers
│ └── index.js
├── node-tests
├── fixtures
│ ├── template.tagname
│ ├── web-component-template.hbs
│ ├── non-standard-extension.handlebars
│ ├── template.hbs
│ ├── template-with-bom.hbs
│ └── compiler.js
├── utils_test.js
├── template_compiler_test.js
├── assertions.js
├── colocated-babel-plugin-test.js
├── colocated-test.js
└── colocated-broccoli-plugin-test.js
├── .watchmanconfig
├── .jshintignore
├── public
└── robots.txt
├── lib
├── index.js
├── plugin-launcher.js
├── template-compiler-plugin.js
├── utils.js
├── index.d.ts
├── ember-addon-main.js
├── colocated-babel-plugin.js
└── colocated-broccoli-plugin.js
├── .stylelintrc.js
├── .stylelintignore
├── .prettierrc.js
├── .template-lintrc.js
├── .eslintignore
├── .ember-cli
├── tsconfig.declarations.json
├── CODE_OF_CONDUCT.md
├── config
└── targets.js
├── .editorconfig
├── .gitignore
├── testem.js
├── .npmignore
├── .github
└── workflows
│ ├── publish.yml
│ └── ci.yml
├── .jshintrc
├── LICENSE.md
├── ember-cli-build.js
├── .eslintrc.js
├── RELEASE.md
├── package.json
├── README.md
└── CHANGELOG.md
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/node-tests/fixtures/template.tagname:
--------------------------------------------------------------------------------
1 | MyCustomElement
--------------------------------------------------------------------------------
/tests/dummy/app/components/pods-comp/template.hbs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/node-tests/fixtures/web-component-template.hbs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/colocation/app/components/bar.hbs:
--------------------------------------------------------------------------------
1 | Hello from bar!
2 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | .git
2 | bower_components
3 | node_modules
4 | tmp
5 |
--------------------------------------------------------------------------------
/tests/colocation/app/components/its-native.hbs:
--------------------------------------------------------------------------------
1 | {{this.greeting}}
2 |
--------------------------------------------------------------------------------
/tests/colocation/app/components/foo.hbs:
--------------------------------------------------------------------------------
1 | Module: {{module-name-inliner}}
2 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/tests/colocation/app/components/baz/index.hbs:
--------------------------------------------------------------------------------
1 | Module: {{module-name-inliner}}
2 |
--------------------------------------------------------------------------------
/tests/colocation/app/components/foo/bar.hbs:
--------------------------------------------------------------------------------
1 | Module: {{module-name-inliner}}
2 |
--------------------------------------------------------------------------------
/node-tests/fixtures/non-standard-extension.handlebars:
--------------------------------------------------------------------------------
1 |
2 | {{name}}
3 |
--------------------------------------------------------------------------------
/tests/colocation/app/components/foo/baz/index.hbs:
--------------------------------------------------------------------------------
1 | Module: {{module-name-inliner}}
2 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/x-module-name-inlined-component.hbs:
--------------------------------------------------------------------------------
1 | {{module-name-inliner}}
2 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/x-module-name-reversed-component.hbs:
--------------------------------------------------------------------------------
1 | {{module-name-reverser}}
2 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/node-tests/fixtures/template.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#foo-bar}}
3 | {{name}}
4 | {{/foo-bar}}
5 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/styleguide.hbs:
--------------------------------------------------------------------------------
1 | You should try out this cool note component
--------------------------------------------------------------------------------
/tests/dummy/app/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember-resolver';
2 |
3 | export default Resolver;
4 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | TemplateCompilerPlugin: require('./template-compiler-plugin'),
3 | };
4 |
--------------------------------------------------------------------------------
/node-tests/fixtures/template-with-bom.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#foo-bar}}
3 | {{name}}
4 | {{/foo-bar}}
5 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 | ember-cli-htmlbars
2 | Run Tests
3 | {{outlet}}
--------------------------------------------------------------------------------
/node-tests/fixtures/compiler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function () {
4 | return 'I AM MODULE OF COMPILER';
5 | };
6 |
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'],
5 | };
6 |
--------------------------------------------------------------------------------
/lib/plugin-launcher.js:
--------------------------------------------------------------------------------
1 | module.exports = function (params) {
2 | return require(params.requireFile)[params.buildUsing](params.params).plugin;
3 | };
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.css:
--------------------------------------------------------------------------------
1 | /* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */
2 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | # unconventional files
2 | /blueprints/*/files/
3 |
4 | # compiled output
5 | /dist/
6 |
7 | # addons
8 | /.node_modules.ember-try/
9 |
--------------------------------------------------------------------------------
/tests/colocation/app/components/bar.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 |
3 | export default Component.extend({
4 | attributeBindings: ['lolol'],
5 | });
6 |
--------------------------------------------------------------------------------
/tests/dummy/config/optional-features.json:
--------------------------------------------------------------------------------
1 | {
2 | "application-template-wrapper": false,
3 | "jquery-integration": false,
4 | "template-only-glimmer-components": true
5 | }
6 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | overrides: [
3 | {
4 | files: '*.{js,ts}',
5 | options: {
6 | singleQuote: true,
7 | },
8 | },
9 | ],
10 | };
11 |
--------------------------------------------------------------------------------
/tests/dummy/lib/module-name-inliner/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "module-name-inliner",
3 | "version": "0.1.0",
4 | "keywords": [
5 | "ember-addon"
6 | ],
7 | "dependencies": {}
8 | }
9 |
--------------------------------------------------------------------------------
/.template-lintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | extends: 'recommended',
5 | rules: {
6 | 'no-curly-component-invocation': false,
7 | 'no-implicit-this': false,
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/tests/colocation/app/components/its-native.js:
--------------------------------------------------------------------------------
1 | import Component from '@ember/component';
2 | import { tracked } from '@glimmer/tracking';
3 |
4 | export default class ItsNative extends Component {
5 | @tracked greeting = 'Hello!';
6 | }
7 |
--------------------------------------------------------------------------------
/tests/dummy/config/targets.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const browsers = [
4 | 'last 1 Chrome versions',
5 | 'last 1 Firefox versions',
6 | 'last 1 Safari versions',
7 | ];
8 |
9 | module.exports = {
10 | browsers,
11 | };
12 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # root dotfiles
2 | !.*.js
3 |
4 | # unconventional js
5 | /blueprints/*/files/
6 |
7 | # compiled output
8 | /dist/
9 |
10 | # misc
11 | /coverage/
12 | !.*
13 | .*/
14 |
15 | # ember-try
16 | /.node_modules.ember-try/
17 |
--------------------------------------------------------------------------------
/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
4 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
5 | */
6 | "isTypeScriptProject": false
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.declarations.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "declarationDir": "declarations",
5 | "emitDeclarationOnly": true,
6 | "noEmit": false,
7 | "rootDir": "."
8 | },
9 | "include": ["addon", "addon-test-support"]
10 | }
11 |
--------------------------------------------------------------------------------
/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import EmberRouter from '@ember/routing/router';
2 | import config from './config/environment';
3 |
4 | const Router = EmberRouter.extend({
5 | location: config.locationType,
6 | rootURL: config.rootURL,
7 | });
8 |
9 | Router.map(function () {
10 | this.route('styleguide');
11 | });
12 |
13 | export default Router;
14 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | The Ember team and community are committed to everyone having a safe and inclusive experience.
2 |
3 | **Our Community Guidelines / Code of Conduct can be found here**:
4 |
5 | http://emberjs.com/guidelines/
6 |
7 | For a history of updates, see the page history here:
8 |
9 | https://github.com/emberjs/website/commits/master/source/guidelines.html.erb
10 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import Application from '../app';
2 | import config from '../config/environment';
3 | import { setApplication } from '@ember/test-helpers';
4 | import { start } from 'ember-qunit';
5 | import * as QUnit from 'qunit';
6 | import { setup } from 'qunit-dom';
7 |
8 | setup(QUnit.assert);
9 |
10 | setApplication(Application.create(config.APP));
11 |
12 | start();
13 |
--------------------------------------------------------------------------------
/config/targets.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const browsers = [
4 | 'last 1 Chrome versions',
5 | 'last 1 Firefox versions',
6 | 'last 1 Safari versions',
7 | ];
8 |
9 | const isCI = !!process.env.CI;
10 | const isProduction = process.env.EMBER_ENV === 'production';
11 |
12 | if (isCI || isProduction) {
13 | browsers.push('ie 11');
14 | }
15 |
16 | module.exports = {
17 | browsers,
18 | };
19 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Application from '@ember/application';
2 | import Resolver from './resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from './config/environment';
5 |
6 | const App = Application.extend({
7 | modulePrefix: config.modulePrefix,
8 | podModulePrefix: config.podModulePrefix,
9 | Resolver,
10 | });
11 |
12 | loadInitializers(App, config.modulePrefix);
13 |
14 | export default App;
15 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.hbs]
17 | insert_final_newline = false
18 |
19 | [*.{diff,md}]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist/
3 | /declarations/
4 |
5 | # dependencies
6 | /node_modules/
7 |
8 | # misc
9 | /.env*
10 | /.pnp*
11 | /.eslintcache
12 | /coverage/
13 | /npm-debug.log*
14 | /testem.log
15 | /yarn-error.log
16 | /.eslintcache
17 |
18 | # ember-try
19 | /.node_modules.ember-try/
20 | /npm-shrinkwrap.json.ember-try
21 | /package.json.ember-try
22 | /package-lock.json.ember-try
23 | /yarn.lock.ember-try
24 |
25 | # broccoli-debug
26 | /DEBUG/
27 |
--------------------------------------------------------------------------------
/tests/acceptance/styleguide-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { visit, currentURL } from '@ember/test-helpers';
3 | import { setupApplicationTest } from 'ember-qunit';
4 |
5 | module('Acceptance | styleguide', function (hooks) {
6 | setupApplicationTest(hooks);
7 |
8 | test('visiting /styleguide', async function (assert) {
9 | await visit('/styleguide');
10 |
11 | assert.strictEqual(currentURL(), '/styleguide');
12 |
13 | assert.dom('[data-test-es-note-heading]').containsText('says...');
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/tests/dummy/config/ember-cli-update.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": "1.0.0",
3 | "packages": [
4 | {
5 | "name": "ember-cli",
6 | "version": "5.5.0",
7 | "blueprints": [
8 | {
9 | "name": "addon",
10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output",
11 | "codemodsSource": "ember-addon-codemods-manifest@1",
12 | "isBaseBlueprint": true,
13 | "options": [
14 | "--yarn",
15 | "--no-welcome"
16 | ]
17 | }
18 | ]
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | test_page: 'tests/index.html?hidepassed',
3 | disable_watching: true,
4 | launch_in_ci: ['Chrome'],
5 | launch_in_dev: ['Chrome'],
6 | browser_start_timeout: 120,
7 | browser_args: {
8 | Chrome: {
9 | ci: [
10 | // --no-sandbox is needed when running Chrome inside a container
11 | process.env.CI ? '--no-sandbox' : null,
12 | '--headless',
13 | '--disable-dev-shm-usage',
14 | '--mute-audio',
15 | '--remote-debugging-port=0',
16 | '--window-size=1440,900',
17 | ].filter(Boolean),
18 | },
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist/
3 | /tmp/
4 |
5 | # misc
6 | /.editorconfig
7 | /.ember-cli
8 | /.eslintignore
9 | /.eslintrc.js
10 | /.git/
11 | /.github/
12 | /.gitignore
13 | /.prettierignore
14 | /.prettierrc.js
15 | /.stylelintignore
16 | /.stylelintrc.js
17 | /.template-lintrc.js
18 | /.travis.yml
19 | /.watchmanconfig
20 | /CONTRIBUTING.md
21 | /ember-cli-build.js
22 | /testem.js
23 | /tests/
24 | /yarn-error.log
25 | /yarn.lock
26 | /node-tests/
27 | .gitkeep
28 |
29 | # ember-try
30 | /.node_modules.ember-try/
31 | /npm-shrinkwrap.json.ember-try
32 | /package.json.ember-try
33 | /package-lock.json.ember-try
34 | /yarn.lock.ember-try
35 |
--------------------------------------------------------------------------------
/tests/integration/components/ast-plugins-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupRenderingTest } from 'ember-qunit';
3 | import { render } from '@ember/test-helpers';
4 | import { hbs } from 'ember-cli-htmlbars';
5 |
6 | module('tests/integration/components/ast-plugins-test', function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test('stand alone templates have AST plugins ran', async function (assert) {
10 | await render(hbs``);
11 |
12 | assert.strictEqual(
13 | this.element.textContent.trim(),
14 | 'dummy/components/x-module-name-inlined-component.hbs',
15 | );
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/node-tests/utils_test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const utils = require('../lib/utils');
4 | const assert = require('assert');
5 |
6 | describe('utils', function () {
7 | describe('isColocatedBabelPluginRegistered', function () {
8 | it('is false when no plugins exist', function () {
9 | let plugins = [];
10 |
11 | assert.strictEqual(
12 | utils.isColocatedBabelPluginRegistered(plugins),
13 | false,
14 | );
15 | });
16 |
17 | it('detects when the plugin exists', function () {
18 | let plugins = [require.resolve('../lib/colocated-babel-plugin')];
19 |
20 | assert.strictEqual(utils.isColocatedBabelPluginRegistered(plugins), true);
21 | });
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/tests/dummy/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Dummy
6 |
7 |
8 |
9 | {{content-for "head"}}
10 |
11 |
12 |
13 |
14 | {{content-for "head-footer"}}
15 |
16 |
17 | {{content-for "body"}}
18 |
19 |
20 |
21 |
22 | {{content-for "body-footer"}}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | publish:
10 | name: Publish to npm
11 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v3
16 |
17 | - name: Install node
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: 16.x
21 | registry-url: 'https://registry.npmjs.org'
22 |
23 | - name: install dependencies
24 | run: yarn install --frozen-lockfile --ignore-engines
25 |
26 | - name: auto-dist-tag
27 | run: npx auto-dist-tag --write
28 |
29 | - name: publish to npm
30 | run: npm publish
31 | env:
32 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
33 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "console",
4 | "it",
5 | "describe",
6 | "beforeEach",
7 | "afterEach",
8 | "before",
9 | "after",
10 | "-Promise"
11 | ],
12 | "proto": true,
13 | "strict": true,
14 | "indent": 2,
15 | "camelcase": true,
16 | "node": true,
17 | "browser": false,
18 | "boss": true,
19 | "curly": true,
20 | "latedef": "nofunc",
21 | "debug": false,
22 | "devel": false,
23 | "eqeqeq": true,
24 | "evil": true,
25 | "forin": false,
26 | "immed": false,
27 | "laxbreak": false,
28 | "newcap": true,
29 | "noarg": true,
30 | "noempty": false,
31 | "quotmark": true,
32 | "nonew": false,
33 | "nomen": false,
34 | "onevar": false,
35 | "plusplus": false,
36 | "regexp": false,
37 | "undef": true,
38 | "unused": true,
39 | "sub": true,
40 | "trailing": true,
41 | "white": false,
42 | "eqnull": true,
43 | "esnext": true
44 | }
45 |
--------------------------------------------------------------------------------
/lib/template-compiler-plugin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Filter = require('broccoli-persistent-filter');
4 | const jsStringEscape = require('js-string-escape');
5 |
6 | class TemplateCompiler extends Filter {
7 | constructor(inputTree, options = {}) {
8 | if (!('persist' in options)) {
9 | options.persist = true;
10 | }
11 | super(inputTree, options);
12 | }
13 |
14 | baseDir() {
15 | return __dirname;
16 | }
17 |
18 | processString(string, relativePath) {
19 | return [
20 | `import { hbs } from 'ember-cli-htmlbars';`,
21 | `export default hbs('${jsStringEscape(
22 | string,
23 | )}', { moduleName: '${jsStringEscape(relativePath)}' });`,
24 | '',
25 | ].join('\n');
26 | }
27 |
28 | getDestFilePath(relativePath) {
29 | if (relativePath.endsWith('.hbs')) {
30 | return relativePath.replace(/\.hbs$/, '.js');
31 | }
32 | }
33 | }
34 |
35 | TemplateCompiler.prototype.extensions = ['hbs', 'handlebars'];
36 | TemplateCompiler.prototype.targetExtension = 'js';
37 |
38 | module.exports = TemplateCompiler;
39 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Robert Jackson and Ember CLI Contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Dummy Tests
6 |
7 |
8 |
9 | {{content-for "head"}}
10 | {{content-for "test-head"}}
11 |
12 |
13 |
14 |
15 |
16 | {{content-for "head-footer"}}
17 | {{content-for "test-head-footer"}}
18 |
19 |
20 | {{content-for "body"}}
21 | {{content-for "test-body"}}
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | {{content-for "body-footer"}}
37 | {{content-for "test-body-footer"}}
38 |
39 |
40 |
--------------------------------------------------------------------------------
/tests/dummy/lib/module-name-inliner/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | name: require('./package').name,
5 |
6 | isDevelopingAddon() {
7 | return true;
8 | },
9 |
10 | setupPreprocessorRegistry(type, registry) {
11 | // can only add the plugin with this style on newer Ember versions
12 | registry.add('htmlbars-ast-plugin', this.buildPlugin());
13 | },
14 |
15 | buildPlugin() {
16 | // https://astexplorer.net/#/gist/7c8399056873e1ddbd2b1acf0a41592a/e08f2f850de449b77b8ad995085496a1100dfd1f
17 | return {
18 | name: 'module-name-inliner',
19 | baseDir() {
20 | return __dirname;
21 | },
22 | parallelBabel: {
23 | requireFile: __filename,
24 | buildUsing: 'buildPlugin',
25 | params: {},
26 | },
27 | plugin(env) {
28 | let { builders } = env.syntax;
29 |
30 | return {
31 | name: 'module-name-inliner',
32 |
33 | visitor: {
34 | PathExpression(node) {
35 | if (node.original === 'module-name-inliner') {
36 | // replacing the path with a string literal, like this
37 | // {{"the-module-name-here"}}
38 | return builders.string(env.moduleName);
39 | }
40 | },
41 | },
42 | };
43 | },
44 | };
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (environment) {
4 | const ENV = {
5 | modulePrefix: 'dummy',
6 | environment,
7 | rootURL: '/',
8 | locationType: 'history',
9 | EmberENV: {
10 | EXTEND_PROTOTYPES: false,
11 | FEATURES: {
12 | // Here you can enable experimental features on an ember canary build
13 | // e.g. 'with-controller': true
14 | },
15 | },
16 |
17 | APP: {
18 | // Here you can pass flags/options to your application instance
19 | // when it is created
20 | },
21 | };
22 |
23 | if (environment === 'development') {
24 | // ENV.APP.LOG_RESOLVER = true;
25 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
26 | // ENV.APP.LOG_TRANSITIONS = true;
27 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
28 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
29 | }
30 |
31 | if (environment === 'test') {
32 | // Testem prefers this...
33 | ENV.locationType = 'none';
34 |
35 | // keep test console output quieter
36 | ENV.APP.LOG_ACTIVE_GENERATION = false;
37 | ENV.APP.LOG_VIEW_LOOKUPS = false;
38 |
39 | ENV.APP.rootElement = '#ember-testing';
40 | ENV.APP.autoboot = false;
41 | }
42 |
43 | if (environment === 'production') {
44 | // here you can enable a production-specific feature
45 | }
46 |
47 | return ENV;
48 | };
49 |
--------------------------------------------------------------------------------
/node-tests/template_compiler_test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const TemplateCompiler = require('../lib/template-compiler-plugin');
5 | const { createTempDir, createBuilder } = require('broccoli-test-helper');
6 | const fixturify = require('fixturify');
7 | const jsStringEscape = require('js-string-escape');
8 |
9 | describe('TemplateCompiler', function () {
10 | this.timeout(10000);
11 |
12 | let input, output, builder;
13 |
14 | beforeEach(async function () {
15 | input = await createTempDir();
16 | input.write(fixturify.readSync(`${__dirname}/fixtures`));
17 | });
18 |
19 | afterEach(async function () {
20 | if (builder) {
21 | builder.cleanup();
22 | }
23 | await input.dispose();
24 | if (output) {
25 | await output.dispose();
26 | }
27 | });
28 |
29 | it('converts hbs into JS', async function () {
30 | let tree = new TemplateCompiler(input.path());
31 |
32 | output = createBuilder(tree);
33 | await output.build();
34 |
35 | let source = input.readText('template.hbs');
36 | let expected = [
37 | `import { hbs } from 'ember-cli-htmlbars';`,
38 | `export default hbs('${jsStringEscape(
39 | source,
40 | )}', { moduleName: 'template.hbs' });`,
41 | '',
42 | ].join('\n');
43 | assert.strictEqual(output.readText('template.js'), expected);
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/tests/helpers/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | setupApplicationTest as upstreamSetupApplicationTest,
3 | setupRenderingTest as upstreamSetupRenderingTest,
4 | setupTest as upstreamSetupTest,
5 | } from 'ember-qunit';
6 |
7 | // This file exists to provide wrappers around ember-qunit's
8 | // test setup functions. This way, you can easily extend the setup that is
9 | // needed per test type.
10 |
11 | function setupApplicationTest(hooks, options) {
12 | upstreamSetupApplicationTest(hooks, options);
13 |
14 | // Additional setup for application tests can be done here.
15 | //
16 | // For example, if you need an authenticated session for each
17 | // application test, you could do:
18 | //
19 | // hooks.beforeEach(async function () {
20 | // await authenticateSession(); // ember-simple-auth
21 | // });
22 | //
23 | // This is also a good place to call test setup functions coming
24 | // from other addons:
25 | //
26 | // setupIntl(hooks); // ember-intl
27 | // setupMirage(hooks); // ember-cli-mirage
28 | }
29 |
30 | function setupRenderingTest(hooks, options) {
31 | upstreamSetupRenderingTest(hooks, options);
32 |
33 | // Additional setup for rendering tests can be done here.
34 | }
35 |
36 | function setupTest(hooks, options) {
37 | upstreamSetupTest(hooks, options);
38 |
39 | // Additional setup for unit tests can be done here.
40 | }
41 |
42 | export { setupApplicationTest, setupRenderingTest, setupTest };
43 |
--------------------------------------------------------------------------------
/tests/dummy/config/ember-try.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const getChannelURL = require('ember-source-channel-url');
4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup');
5 |
6 | module.exports = async function () {
7 | return {
8 | useYarn: true,
9 | scenarios: [
10 | {
11 | name: 'ember-lts-4.12',
12 | npm: {
13 | devDependencies: {
14 | 'ember-source': '~4.12.0',
15 | 'ember-cli': '~4.12.0',
16 | },
17 | },
18 | },
19 | {
20 | name: 'ember-release',
21 | npm: {
22 | devDependencies: {
23 | 'ember-source': await getChannelURL('release'),
24 | '@ember/string': '^3.1.1',
25 | },
26 | ember: {
27 | edition: 'octane',
28 | },
29 | },
30 | },
31 | {
32 | name: 'ember-beta',
33 | npm: {
34 | devDependencies: {
35 | 'ember-source': await getChannelURL('beta'),
36 | '@ember/string': '^3.1.1',
37 | },
38 | ember: {
39 | edition: 'octane',
40 | },
41 | },
42 | },
43 | {
44 | name: 'ember-canary',
45 | npm: {
46 | devDependencies: {
47 | 'ember-source': await getChannelURL('canary'),
48 | '@ember/string': '^3.1.1',
49 | },
50 | ember: {
51 | edition: 'octane',
52 | },
53 | },
54 | },
55 | embroiderSafe(),
56 | embroiderOptimized(),
57 | ],
58 | };
59 | };
60 |
--------------------------------------------------------------------------------
/tests/integration/components/test-inline-precompile-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupRenderingTest } from 'ember-qunit';
3 | import { render } from '@ember/test-helpers';
4 | import hbsOne from 'htmlbars-inline-precompile';
5 | import hbsTwo from 'ember-cli-htmlbars-inline-precompile';
6 | import { hbs as hbsThree } from 'ember-cli-htmlbars';
7 | import { precompileTemplate } from '@ember/template-compilation';
8 |
9 | module('tests/integration/components/test-inline-precompile', function (hooks) {
10 | setupRenderingTest(hooks);
11 |
12 | test('htmlbars-inline-precompile works', async function (assert) {
13 | await render(hbsOne`Wheeeee`);
14 |
15 | assert.strictEqual(this.element.textContent.trim(), 'Wheeeee');
16 | });
17 |
18 | test('ember-cli-htmlbars-inline-precompile works', async function (assert) {
19 | await render(hbsTwo`Wheeeee`);
20 |
21 | assert.strictEqual(this.element.textContent.trim(), 'Wheeeee');
22 | });
23 |
24 | test('ember-cli-htmlbars works', async function (assert) {
25 | await render(hbsThree`Wheeeee`);
26 |
27 | assert.strictEqual(this.element.textContent.trim(), 'Wheeeee');
28 | });
29 |
30 | test('precompileTemplate', async function (assert) {
31 | await render(precompileTemplate(`Wheeeee`, {}));
32 |
33 | assert.strictEqual(this.element.textContent.trim(), 'Wheeeee');
34 | });
35 |
36 | test('inline templates have AST plugins ran', async function (assert) {
37 | await render(
38 | hbsThree('{{module-name-inliner}}', { moduleName: 'hello-template.hbs' }),
39 | );
40 |
41 | assert.strictEqual(this.element.textContent.trim(), 'hello-template.hbs');
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/node-tests/assertions.js:
--------------------------------------------------------------------------------
1 | const { Assertion, expect } = require('chai');
2 | const { codeEqual } = require('code-equality-assertions');
3 |
4 | // code-equality-assertions comes with integrations for qunit and jest. This
5 | // test suite was using a mix of chai and Node build-in assert.
6 |
7 | function assertEqualCode(actual, expected, message = '') {
8 | let status = codeEqual(actual, expected);
9 | this.assert(status.result, message + status.diff);
10 | }
11 |
12 | Assertion.addMethod('toEqualCode', function (expectedSrc) {
13 | assertEqualCode.call(this, this._obj, expectedSrc);
14 | });
15 |
16 | // need this because there are tests for coffeescript 🙄
17 | function simpleNormalize(s) {
18 | return s.trim().replace(/\n\s+/g, '\n');
19 | }
20 |
21 | function assertNonJSEqual(actual, expected, message) {
22 | actual = simpleNormalize(actual);
23 | expected = simpleNormalize(expected);
24 | this.assert(actual === expected, message, message, actual, expected);
25 | }
26 |
27 | function deepEqualCode(actual, expected, message = '') {
28 | for (let [key, value] of Object.entries(expected)) {
29 | if (typeof value === 'string') {
30 | if (key.endsWith('.js') || key.endsWith('.ts')) {
31 | assertEqualCode.call(this, actual[key], value, `${message}->${key}`);
32 | } else {
33 | assertNonJSEqual.call(this, actual[key], value, `${message}->${key}`);
34 | }
35 | } else {
36 | deepEqualCode.call(this, actual[key], value, `${message}->${key}`);
37 | }
38 | }
39 | }
40 |
41 | Assertion.addMethod('toDeepEqualCode', function (expectedTree) {
42 | deepEqualCode.call(this, this._obj, expectedTree);
43 | });
44 |
45 | exports.expect = expect;
46 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
4 | const MergeTree = require('broccoli-merge-trees');
5 | const { has } = require('@ember/edition-utils');
6 |
7 | module.exports = function (defaults) {
8 | let hasOctane = has('octane');
9 | let appTree = 'tests/dummy/app';
10 | if (hasOctane) {
11 | appTree = new MergeTree(['tests/dummy/app', 'tests/colocation/app']);
12 | }
13 |
14 | let app = new EmberAddon(defaults, {
15 | // Add options here
16 | 'ember-cli-babel': {
17 | throwUnlessParallelizable: true,
18 | },
19 |
20 | trees: {
21 | app: appTree,
22 | },
23 |
24 | babel: {
25 | plugins: [
26 | [
27 | require.resolve('babel-plugin-debug-macros'),
28 | {
29 | flags: [
30 | {
31 | name: '@ember/edition-fake-module',
32 | source: '@ember/edition-fake-module',
33 | flags: {
34 | HAS_OCTANE: hasOctane,
35 | },
36 | },
37 | ],
38 | },
39 | 'debug macros - octane flag',
40 | ],
41 | ],
42 | },
43 | });
44 |
45 | /*
46 | This build file specifies the options for the dummy test app of this
47 | addon, located in `/tests/dummy`
48 | This build file does *not* influence how the addon or the app using it
49 | behave. You most likely want to be modifying `./index.js` or app's build file
50 | */
51 |
52 | const { maybeEmbroider } = require('@embroider/test-setup');
53 | return maybeEmbroider(app, {
54 | skipBabel: [
55 | {
56 | package: 'qunit',
57 | },
58 | ],
59 | });
60 | };
61 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | function isTemplateCompilationPluginRegistered(plugins) {
4 | return plugins.some((plugin) => {
5 | if (Array.isArray(plugin)) {
6 | let [pluginPathOrInstance, , key] = plugin;
7 | return (
8 | pluginPathOrInstance ===
9 | require.resolve('babel-plugin-ember-template-compilation') &&
10 | key === 'ember-cli-htmlbars:inline-precompile'
11 | );
12 | } else {
13 | return false;
14 | }
15 | });
16 | }
17 |
18 | function isColocatedBabelPluginRegistered(plugins) {
19 | return plugins.some((plugin) => {
20 | let path = Array.isArray(plugin) ? plugin[0] : plugin;
21 |
22 | return (
23 | typeof path === 'string' &&
24 | path === require.resolve('./colocated-babel-plugin')
25 | );
26 | });
27 | }
28 |
29 | function convertPlugins(wrappers) {
30 | let launcher = require.resolve('./plugin-launcher.js');
31 | return (
32 | wrappers
33 | .map((wrapper) => {
34 | if (wrapper.requireFile) {
35 | return [
36 | launcher,
37 | {
38 | requireFile: wrapper.requireFile,
39 | buildUsing: wrapper.buildUsing,
40 | params: wrapper.params,
41 | },
42 | ];
43 | }
44 | if (wrapper.parallelBabel) {
45 | return [
46 | launcher,
47 | {
48 | requireFile: wrapper.parallelBabel.requireFile,
49 | buildUsing: wrapper.parallelBabel.buildUsing,
50 | params: wrapper.parallelBabel.params,
51 | },
52 | ];
53 | }
54 | return wrapper.plugin;
55 | })
56 | // For historic reasons, our plugins are stored in reverse order, whereas
57 | // babel-plugin-ember-template-compilation uses the sensible order.
58 | .reverse()
59 | );
60 | }
61 |
62 | module.exports = {
63 | convertPlugins,
64 | isColocatedBabelPluginRegistered,
65 | isTemplateCompilationPluginRegistered,
66 | };
67 |
--------------------------------------------------------------------------------
/tests/integration/components/colocation-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupRenderingTest } from 'ember-qunit';
3 | import { render } from '@ember/test-helpers';
4 | import { hbs } from 'ember-cli-htmlbars';
5 | import { HAS_OCTANE } from '@ember/edition-fake-module';
6 |
7 | module('tests/integration/components/test-inline-precompile', function (hooks) {
8 | if (!HAS_OCTANE) {
9 | // can only run against 3.13+ (due to colocation support)
10 | return;
11 | }
12 |
13 | setupRenderingTest(hooks);
14 |
15 | test('registered ast plugins run against colocated templates (template-only)', async function (assert) {
16 | await render(hbs``);
17 |
18 | assert.strictEqual(
19 | this.element.textContent.trim(),
20 | 'Module: dummy/components/foo.hbs',
21 | );
22 | });
23 |
24 | test('registered ast plugins run against nested colocated templates (template-only)', async function (assert) {
25 | await render(hbs``);
26 |
27 | assert.strictEqual(
28 | this.element.textContent.trim(),
29 | 'Module: dummy/components/foo/bar.hbs',
30 | );
31 | });
32 |
33 | test('registered ast plugins run against colocated template index files (template-only)', async function (assert) {
34 | await render(hbs``);
35 |
36 | assert.strictEqual(
37 | this.element.textContent.trim(),
38 | 'Module: dummy/components/baz/index.hbs',
39 | );
40 | });
41 |
42 | test('registered ast plugins run against nested colocated template index files (template-only)', async function (assert) {
43 | await render(hbs``);
44 |
45 | assert.strictEqual(
46 | this.element.textContent.trim(),
47 | 'Module: dummy/components/foo/baz/index.hbs',
48 | );
49 | });
50 |
51 | test('can invoke native class based component with decorators', async function (assert) {
52 | await render(hbs``);
53 |
54 | assert.strictEqual(this.element.textContent.trim(), 'Hello!');
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@babel/eslint-parser',
4 | parserOptions: {
5 | ecmaVersion: 'latest',
6 | sourceType: 'module',
7 | requireConfigFile: false,
8 | babelOptions: {
9 | plugins: [
10 | ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],
11 | ],
12 | },
13 | },
14 | plugins: ['ember', 'prettier'],
15 | extends: [
16 | 'eslint:recommended',
17 | 'plugin:ember/recommended',
18 | 'plugin:prettier/recommended',
19 | ],
20 | env: {
21 | browser: true,
22 | },
23 | rules: {
24 | 'ember/no-classic-components': 'off',
25 | 'ember/no-classic-classes': 'off',
26 | 'ember/require-tagless-components': 'off',
27 | },
28 | overrides: [
29 | // node files
30 | {
31 | files: [
32 | './.eslintrc.js',
33 | './.prettierrc.js',
34 | './.stylelintrc.js',
35 | './.template-lintrc.js',
36 | 'colocated-broccoli-plugin.js',
37 | './ember-cli-build.js',
38 | 'lib/**/*.js',
39 | './testem.js',
40 | './blueprints/*/index.js',
41 | './config/**/*.js',
42 | './tests/dummy/config/**/*.js',
43 | 'tests/dummy/lib/**/*.js',
44 | ],
45 | parserOptions: {
46 | sourceType: 'script',
47 | ecmaVersion: 2018,
48 | },
49 | env: {
50 | browser: false,
51 | node: true,
52 | },
53 | extends: ['plugin:n/recommended'],
54 | },
55 |
56 | // node files
57 | {
58 | files: ['node-tests/**/*.js'],
59 | parserOptions: {
60 | sourceType: 'script',
61 | ecmaVersion: 2018,
62 | },
63 | env: {
64 | browser: false,
65 | node: true,
66 | mocha: true,
67 | },
68 | plugins: ['mocha'],
69 | extends: ['plugin:n/recommended'],
70 | },
71 | {
72 | // test files
73 | files: ['tests/**/*-test.{js,ts}'],
74 | extends: ['plugin:qunit/recommended'],
75 | },
76 | ],
77 | };
78 |
--------------------------------------------------------------------------------
/lib/index.d.ts:
--------------------------------------------------------------------------------
1 | // Shut off automatically-export-everything-mode. We only want to export the
2 | // things we explicitly *say* to export.
3 | export {};
4 |
5 | // Allows us to create a branded/opaque type which can *only* be constructed
6 | // without an unsafe cast by calling `hbs()`.
7 | declare const Data: unique symbol;
8 |
9 | /**
10 | The result of calling `hbs()`: an internal type for Ember to use with other
11 | framework-level APIs (public and private) like `setComponentTemplate()`.
12 |
13 | This type is *not* user-constructible; it is only legal to get it from `hbs`.
14 | */
15 | declare class TemplateFactory {
16 | private [Data]: 'template-factory';
17 | }
18 |
19 | // Only export the type side of the class, so that callers are not misled into
20 | // thinking that they can instantiate, subclass, etc.
21 | export type { TemplateFactory };
22 |
23 | export interface PrecompileOptions {
24 | moduleName?: string;
25 | parseOptions?: {
26 | srcName?: string;
27 | };
28 | }
29 |
30 | /**
31 | * A helper for rendering components.
32 | *
33 | * @param tagged The template to render.
34 | *
35 | * ## Usage
36 | *
37 | * ### With tagged template
38 | *
39 | * ```ts
40 | * import { module, test } from 'qunit';
41 | * import { setupRenderingTest } from 'ember-qunit';
42 | * import { render } from '@ember/test-helpers';
43 | * import { hbs } from 'ember-cli-htmlbars';
44 | *
45 | * module('demonstrate hbs usage', function(hooks) {
46 | * setupRenderingTest(hooks);
47 | *
48 | * test('you can render things', function(assert) {
49 | * await render(hbs``);
50 | * assert.ok(true);
51 | * });
52 | * });
53 | * ```
54 | *
55 | * ## With string and options
56 | *
57 | * ```ts
58 | * import Component from '@glimmer/component';
59 | * import { setComponentTemplate } from '@ember/component';
60 | * import { hbs } from 'ember-cli-htmlbars';
61 | *
62 | * class Hello extends Component {
63 | * greeting = 'hello world';
64 | * }
65 | *
66 | * setComponentTemplate(
67 | * hbs('{{this.greeting}}
', { moduleName: 'hello.hbs' }),
68 | * MyComponent
69 | * );
70 | * ```
71 | */
72 | export function hbs(template: string, options?: PrecompileOptions): TemplateFactory;
73 | export function hbs(tagged: TemplateStringsArray): TemplateFactory;
74 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | # Release Process
2 |
3 | Releases are mostly automated using
4 | [release-it](https://github.com/release-it/release-it/) and
5 | [lerna-changelog](https://github.com/lerna/lerna-changelog/).
6 |
7 | ## Preparation
8 |
9 | Since the majority of the actual release process is automated, the primary
10 | remaining task prior to releasing is confirming that all pull requests that
11 | have been merged since the last release have been labeled with the appropriate
12 | `lerna-changelog` labels and the titles have been updated to ensure they
13 | represent something that would make sense to our users. Some great information
14 | on why this is important can be found at
15 | [keepachangelog.com](https://keepachangelog.com/en/1.0.0/), but the overall
16 | guiding principle here is that changelogs are for humans, not machines.
17 |
18 | When reviewing merged PR's the labels to be used are:
19 |
20 | * breaking - Used when the PR is considered a breaking change.
21 | * enhancement - Used when the PR adds a new feature or enhancement.
22 | * bug - Used when the PR fixes a bug included in a previous release.
23 | * documentation - Used when the PR adds or updates documentation.
24 | * internal - Used for internal changes that still require a mention in the
25 | changelog/release notes.
26 |
27 | ## Release
28 |
29 | Once the prep work is completed, the actual release is straight forward:
30 |
31 | * First, ensure that you have installed your projects dependencies:
32 |
33 | ```sh
34 | yarn install
35 | ```
36 |
37 | * Second, ensure that you have obtained a
38 | [GitHub personal access token][generate-token] with the `repo` scope (no
39 | other permissions are needed). Make sure the token is available as the
40 | `GITHUB_AUTH` environment variable.
41 |
42 | For instance:
43 |
44 | ```bash
45 | export GITHUB_AUTH=abc123def456
46 | ```
47 |
48 | [generate-token]: https://github.com/settings/tokens/new?scopes=repo&description=GITHUB_AUTH+env+variable
49 |
50 | * And last (but not least 😁) do your release.
51 |
52 | ```sh
53 | npx release-it
54 | ```
55 |
56 | [release-it](https://github.com/release-it/release-it/) manages the actual
57 | release process. It will prompt you to to choose the version number after which
58 | you will have the chance to hand tweak the changelog to be used (for the
59 | `CHANGELOG.md` and GitHub release), then `release-it` continues on to tagging,
60 | pushing the tag and commits, etc.
61 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - main
8 | - "v*"
9 | pull_request: {}
10 | schedule:
11 | - cron: "0 6 * * 0" # weekly, on sundays
12 |
13 | jobs:
14 | lint:
15 | name: Linting
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v3
20 | - uses: actions/setup-node@v3
21 | with:
22 | node-version: 20.x
23 | - name: install dependencies
24 | run: yarn install --frozen-lockfile
25 | - name: lint:js
26 | run: yarn lint:js
27 | - name: lint:hbs
28 | run: yarn lint:hbs
29 |
30 | test:
31 | name: Tests
32 | runs-on: ${{ matrix.os }}-latest
33 |
34 | strategy:
35 | fail-fast: false
36 | matrix:
37 | os: [ubuntu, windows]
38 | node-version: [20.x, 24.x]
39 |
40 | steps:
41 | - uses: actions/checkout@v3
42 | - uses: actions/setup-node@v3
43 | with:
44 | node-version: ${{ matrix.node-version }}
45 | - name: install dependencies
46 | run: yarn install --ignore-engines --frozen-lockfile
47 | - name: node tests
48 | run: yarn test:node
49 | - name: ember test
50 | run: yarn test:ember
51 |
52 | floating-dependencies:
53 | name: Floating Deps
54 | runs-on: ubuntu-latest
55 |
56 | needs: [test, lint]
57 |
58 | steps:
59 | - uses: actions/checkout@v3
60 | - uses: actions/setup-node@v3
61 | with:
62 | node-version: 20.x
63 | - name: install dependencies
64 | run: yarn install --ignore-lockfile
65 | - name: node tests
66 | run: yarn test:node
67 | - name: ember test
68 | run: yarn test:ember
69 |
70 | try-scenarios:
71 | name: ${{ matrix.ember-try-scenario }}
72 |
73 | runs-on: ubuntu-latest
74 |
75 | needs: [test, lint]
76 |
77 | strategy:
78 | fail-fast: false
79 | matrix:
80 | ember-try-scenario:
81 | - ember-lts-4.12
82 | - ember-release
83 | - ember-beta
84 | - ember-canary
85 | - embroider-safe
86 | - embroider-optimized
87 |
88 | steps:
89 | - uses: actions/checkout@v3
90 | - uses: actions/setup-node@v3
91 | with:
92 | node-version: 20.x
93 | - name: install dependencies
94 | run: yarn install
95 | - name: test
96 | env:
97 | EMBER_TRY_SCENARIO: ${{ matrix.ember-try-scenario }}
98 | run: node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO
99 |
--------------------------------------------------------------------------------
/lib/ember-addon-main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const SilentError = require('silent-error');
5 | const utils = require('./utils');
6 |
7 | let registryInvocationCounter = 0;
8 |
9 | module.exports = {
10 | name: require('../package').name,
11 |
12 | parentRegistry: null,
13 |
14 | setupPreprocessorRegistry(type, registry) {
15 | // when this.parent === this.project, `this.parent.name` is a function 😭
16 | let parentName =
17 | typeof this.parent.name === 'function'
18 | ? this.parent.name()
19 | : this.parent.name;
20 |
21 | registry.add('template', {
22 | name: 'ember-cli-htmlbars',
23 | ext: 'hbs',
24 | toTree: (tree) => {
25 | const ColocatedTemplateProcessor = require('./colocated-broccoli-plugin');
26 | const TemplateCompiler = require('./template-compiler-plugin');
27 |
28 | let debugTree = require('broccoli-debug').buildDebugCallback(
29 | `ember-cli-htmlbars:${parentName}:tree-${registryInvocationCounter++}`,
30 | );
31 |
32 | let inputTree = debugTree(tree, '01-input');
33 | let colocatedTree = debugTree(
34 | new ColocatedTemplateProcessor(inputTree),
35 | '02-colocated-output',
36 | );
37 | let output = debugTree(
38 | new TemplateCompiler(colocatedTree),
39 | '03-output',
40 | );
41 | return output;
42 | },
43 | });
44 |
45 | if (type === 'parent') {
46 | this.parentRegistry = registry;
47 | }
48 | },
49 |
50 | included() {
51 | this._super.included.apply(this, arguments);
52 |
53 | let addonOptions = this._getAddonOptions();
54 | addonOptions.babel = addonOptions.babel || {};
55 | addonOptions.babel.plugins = addonOptions.babel.plugins || [];
56 | let babelPlugins = addonOptions.babel.plugins;
57 |
58 | if (!utils.isTemplateCompilationPluginRegistered(babelPlugins)) {
59 | babelPlugins.push([
60 | require.resolve('babel-plugin-ember-template-compilation'),
61 | {
62 | // For historic reasons, our plugins are stored in reverse order, whereas
63 | // babel-plugin-ember-template-compilation uses the sensible order.
64 | transforms: this.astPlugins(),
65 | compilerPath: require.resolve(this.templateCompilerPath()),
66 | enableLegacyModules: [
67 | 'ember-cli-htmlbars',
68 | 'ember-cli-htmlbars-inline-precompile',
69 | 'htmlbars-inline-precompile',
70 | ],
71 | },
72 | 'ember-cli-htmlbars:inline-precompile',
73 | ]);
74 | }
75 |
76 | if (!utils.isColocatedBabelPluginRegistered(babelPlugins)) {
77 | babelPlugins.push(require.resolve('./colocated-babel-plugin'));
78 | }
79 | },
80 |
81 | _getAddonOptions() {
82 | return (
83 | (this.parent && this.parent.options) ||
84 | (this.app && this.app.options) ||
85 | {}
86 | );
87 | },
88 |
89 | templateCompilerPath() {
90 | let app = this._findHost();
91 | let templateCompilerPath =
92 | app &&
93 | app.options &&
94 | app.options['ember-cli-htmlbars'] &&
95 | app.options['ember-cli-htmlbars'].templateCompilerPath;
96 |
97 | if (templateCompilerPath) {
98 | return path.resolve(this.project.root, templateCompilerPath);
99 | }
100 |
101 | let ember = this.project.findAddonByName('ember-source');
102 | if (!ember) {
103 | throw new SilentError(
104 | `ember-cli-htmlbars: Cannot find the ember-source addon as part of the project, please ensure that 'ember-source' is in your projects dependencies or devDependencies`,
105 | );
106 | }
107 |
108 | return ember.absolutePaths.templateCompiler;
109 | },
110 |
111 | astPlugins() {
112 | let pluginWrappers = this.parentRegistry.load('htmlbars-ast-plugin');
113 | return utils.convertPlugins(pluginWrappers);
114 | },
115 | };
116 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-cli-htmlbars",
3 | "version": "7.0.0",
4 | "description": "A library for adding htmlbars to ember CLI",
5 | "keywords": [
6 | "ember-addon",
7 | "ember-cli"
8 | ],
9 | "homepage": "https://github.com/ember-cli/ember-cli-htmlbars",
10 | "bugs": {
11 | "url": "https://github.com/ember-cli/ember-cli-htmlbars/issues"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git@github.com:ember-cli/ember-cli-htmlbars.git"
16 | },
17 | "license": "MIT",
18 | "author": "Jonathan Jackson & Chase McCarthy",
19 | "main": "lib/index.js",
20 | "types": "lib/index.d.ts",
21 | "files": [
22 | "lib/"
23 | ],
24 | "scripts": {
25 | "build": "ember build",
26 | "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"",
27 | "lint:css": "stylelint \"**/*.css\"",
28 | "lint:css:fix": "concurrently \"npm:lint:css -- --fix\"",
29 | "lint:hbs": "ember-template-lint .",
30 | "lint:js": "eslint --cache .",
31 | "start": "ember serve",
32 | "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
33 | "test:ember": "ember test",
34 | "test:node": "mocha node-tests/*.js"
35 | },
36 | "dependencies": {
37 | "@ember/edition-utils": "^1.2.0",
38 | "babel-plugin-ember-template-compilation": "^2.0.0",
39 | "broccoli-debug": "^0.6.5",
40 | "broccoli-persistent-filter": "^3.1.2",
41 | "broccoli-plugin": "^4.0.3",
42 | "fs-tree-diff": "^2.0.1",
43 | "heimdalljs-logger": "^0.1.10",
44 | "js-string-escape": "^1.0.1",
45 | "silent-error": "^1.1.1",
46 | "walk-sync": "^4.0.1"
47 | },
48 | "devDependencies": {
49 | "@babel/core": "^7.23.6",
50 | "@babel/eslint-parser": "^7.23.3",
51 | "@babel/plugin-proposal-decorators": "^7.23.6",
52 | "@babel/plugin-transform-class-properties": "^7.23.3",
53 | "@babel/plugin-transform-runtime": "^7.13.15",
54 | "@babel/plugin-transform-typescript": "^7.10.1",
55 | "@babel/runtime": "^7.13.8",
56 | "@ember/optional-features": "^2.0.0",
57 | "@ember/test-helpers": "^3.2.1",
58 | "@embroider/test-setup": "^3.0.3",
59 | "babel-plugin-debug-macros": "^0.3.3",
60 | "broccoli-merge-trees": "^4.2.0",
61 | "broccoli-test-helper": "^2.0.0",
62 | "chai": "^4.3.4",
63 | "code-equality-assertions": "^0.9.0",
64 | "concurrently": "^8.2.2",
65 | "ember-auto-import": "^2.7.0",
66 | "ember-cli": "~5.5.0",
67 | "ember-cli-babel": "^8.2.0",
68 | "ember-cli-clean-css": "^3.0.0",
69 | "ember-cli-dependency-checker": "^3.3.2",
70 | "ember-cli-inject-live-reload": "^2.1.0",
71 | "ember-load-initializers": "^2.1.1",
72 | "ember-qunit": "^8.0.2",
73 | "ember-resolver": "^11.0.1",
74 | "ember-source": "~5.5.0",
75 | "ember-source-channel-url": "^3.0.0",
76 | "ember-styleguide": "^8.4.0",
77 | "ember-template-lint": "^5.13.0",
78 | "ember-try": "^3.0.0",
79 | "eslint": "^8.55.0",
80 | "eslint-config-prettier": "^9.1.0",
81 | "eslint-plugin-ember": "^11.11.1",
82 | "eslint-plugin-mocha": "^8.0.0",
83 | "eslint-plugin-n": "^16.4.0",
84 | "eslint-plugin-prettier": "^5.0.1",
85 | "eslint-plugin-qunit": "^8.0.1",
86 | "fixturify": "^2.1.1",
87 | "loader.js": "^4.7.0",
88 | "mocha": "^8.4.0",
89 | "module-name-inliner": "link:./tests/dummy/lib/module-name-inliner",
90 | "prettier": "^3.1.1",
91 | "qunit": "^2.20.0",
92 | "qunit-dom": "^2.0.0",
93 | "release-it": "^14.2.1",
94 | "release-it-lerna-changelog": "^3.1.0",
95 | "stylelint": "^15.11.0",
96 | "stylelint-config-standard": "^34.0.0",
97 | "stylelint-prettier": "^4.1.0",
98 | "webpack": "^5.89.0"
99 | },
100 | "peerDependencies": {
101 | "@babel/core": ">= 7",
102 | "ember-source": ">= 4.0.0"
103 | },
104 | "engines": {
105 | "node": ">= 20"
106 | },
107 | "publishConfig": {
108 | "registry": "https://registry.npmjs.org"
109 | },
110 | "ember": {
111 | "edition": "octane"
112 | },
113 | "ember-addon": {
114 | "main": "lib/ember-addon-main.js",
115 | "configPath": "tests/dummy/config"
116 | },
117 | "resolutions": {
118 | "ember-cli-htmlbars": "link:."
119 | },
120 | "release-it": {
121 | "plugins": {
122 | "release-it-lerna-changelog": {
123 | "infile": "CHANGELOG.md",
124 | "launchEditor": true
125 | }
126 | },
127 | "git": {
128 | "tagName": "v${version}"
129 | },
130 | "github": {
131 | "release": true,
132 | "tokenRef": "GITHUB_AUTH"
133 | },
134 | "npm": {
135 | "publish": false
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ember CLI HTMLBars
2 |
3 |
4 |
5 | ## Compatibility
6 |
7 | * Ember.js v4.12 or above
8 | * Ember CLI v4.12 or above
9 | * `@embroider/compat` 3.4.3 or above (optional)
10 | * Node.js v20 or above
11 |
12 | ## Adding Custom Plugins
13 |
14 | You can add custom plugins to be used during transpilation of the `addon/` or
15 | `addon-test-support/` trees of your addon (or the `app/` and `tests/` trees of an application)
16 | by registering a custom AST transform.
17 |
18 | ```js
19 | var SomeTransform = require('./some-path/transform');
20 |
21 | module.exports = {
22 | name: 'my-addon-name',
23 |
24 | included: function() {
25 | // we have to wrap these in an object so the ember-cli
26 | // registry doesn't try to call `new` on them (new is actually
27 | // called within htmlbars when compiling a given template).
28 | this.app.registry.add('htmlbars-ast-plugin', {
29 | name: 'some-transform',
30 | plugin: SomeTransform
31 | });
32 |
33 | this._super.included.apply(this, arguments);
34 | }
35 | };
36 | ```
37 |
38 | ### Options for registering a plugin
39 |
40 | * `name` - String. The name of the AST transform for debugging purposes.
41 | * `plugin` - A function of type [`ASTPluginBuilder`](https://github.com/glimmerjs/glimmer-vm/blob/v0.83.1/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts#L314-L320).
42 | * `dependencyInvalidation` - Boolean. A flag that indicates the AST Plugin may, on a per-template basis, depend on other files that affect its output.
43 | * `cacheKey` - function that returns any JSON-compatible value - The value returned is used to invalidate the persistent cache across restarts, usually in the case of a dependency or configuration change.
44 | * `baseDir` - `() => string`. A function that returns the directory on disk of the npm module for the plugin. If provided, a basic cache invalidation is performed if any of the dependencies change (e.g. due to a npm install/upgrade).
45 |
46 | ### Implementing Dependency Invalidation in an AST Plugin
47 |
48 | Plugins that set the `dependencyInvalidation` option to `true` can provide function for the `plugin` of type `ASTDependencyPlugin` as given below.
49 |
50 | Note: the `plugin` function is invoked without a value for `this` in context.
51 |
52 | ```ts
53 | import {ASTPluginBuilder, ASTPlugin} from "@glimmer/syntax/dist/types/lib/parser/tokenizer-event-handlers";
54 |
55 | export type ASTDependencyPlugin = ASTPluginWithDepsBuilder | ASTPluginBuilderWithDeps;
56 |
57 | export interface ASTPluginWithDepsBuilder {
58 | (env: ASTPluginEnvironment): ASTPluginWithDeps;
59 | }
60 |
61 | export interface ASTPluginBuilderWithDeps extends ASTPluginBuilder {
62 | /**
63 | * @see {ASTPluginWithDeps.dependencies} below.
64 | **/
65 | dependencies(relativePath): string[];
66 | }
67 |
68 | export interface ASTPluginWithDeps extends ASTPlugin {
69 | /**
70 | * If this method exists, it is called with the relative path to the current
71 | * file just before processing starts. Use this method to reset the
72 | * dependency tracking state associated with the file.
73 | */
74 | resetDependencies?(relativePath: string): void;
75 | /**
76 | * This method is called just as the template finishes being processed.
77 | *
78 | * @param relativePath {string} A relative path to the file that may have dependencies.
79 | * @return {string[]} paths to files that are a dependency for the given
80 | * file. Any relative paths returned by this method are taken to be relative
81 | * to the file that was processed.
82 | */
83 | dependencies(relativePath: string): string[];
84 | }
85 | ```
86 |
87 | ### Custom Template Compiler
88 |
89 | You can still provide a custom path to the template compiler (e.g. to test
90 | custom template compiler tweaks in an application) by:
91 |
92 | ```js
93 | // ember-cli-build.js
94 |
95 | module.exports = function(defaults) {
96 | let app = new EmberApp(defaults, {
97 | 'ember-cli-htmlbars': {
98 | templateCompilerPath: `some_path/to/ember-template-compiler.js`,
99 | }
100 | });
101 | };
102 | ```
103 |
104 | ## Using as a Broccoli Plugin
105 |
106 | ```javascript
107 | var HtmlbarsCompiler = require('ember-cli-htmlbars');
108 |
109 | var templateTree = new HtmlbarsCompiler('app/templates', {
110 | isHTMLBars: true,
111 |
112 | // provide the templateCompiler that is paired with your Ember version
113 | templateCompiler: require('./bower_components/ember/ember-template-compiler')
114 | });
115 | ```
116 |
--------------------------------------------------------------------------------
/lib/colocated-babel-plugin.js:
--------------------------------------------------------------------------------
1 | // For ease of debuggin / tweaking:
2 | // https://astexplorer.net/#/gist/bcca584efdab6c981a75618642c76a22/1e1d262eaeb47b7da66150e0781a02b96e597b25
3 | module.exports = function (babel) {
4 | let t = babel.types;
5 |
6 | function makeSetComponentTemplateExpression(state) {
7 | return state._colocationEnsureImport(
8 | 'setComponentTemplate',
9 | '@ember/component',
10 | );
11 | }
12 |
13 | function makeColocatedTemplateIdentifier() {
14 | return t.identifier('__COLOCATED_TEMPLATE__');
15 | }
16 |
17 | return {
18 | name: 'ember-cli-htmlbars-colocation-template',
19 |
20 | visitor: {
21 | Program(path, state) {
22 | let allAddedImports = {};
23 |
24 | state._colocationEnsureImport = (exportName, moduleName) => {
25 | let addedImports = (allAddedImports[moduleName] =
26 | allAddedImports[moduleName] || {});
27 |
28 | if (addedImports[exportName])
29 | return t.identifier(addedImports[exportName].name);
30 |
31 | let importDeclarations = path
32 | .get('body')
33 | .filter((n) => n.type === 'ImportDeclaration');
34 |
35 | let preexistingImportDeclaration = importDeclarations.find(
36 | (n) => n.get('source').get('value').node === moduleName,
37 | );
38 |
39 | if (preexistingImportDeclaration) {
40 | let importSpecifier = preexistingImportDeclaration
41 | .get('specifiers')
42 | .find(({ node }) => {
43 | return exportName === 'default'
44 | ? t.isImportDefaultSpecifier(node)
45 | : node.imported && node.imported.name === exportName;
46 | });
47 |
48 | if (importSpecifier) {
49 | addedImports[exportName] = importSpecifier.node.local;
50 | }
51 | }
52 |
53 | if (!addedImports[exportName]) {
54 | let uid = path.scope.generateUidIdentifier(
55 | exportName === 'default' ? moduleName : exportName,
56 | );
57 | addedImports[exportName] = uid;
58 |
59 | let newImportSpecifier =
60 | exportName === 'default'
61 | ? t.importDefaultSpecifier(uid)
62 | : t.importSpecifier(uid, t.identifier(exportName));
63 |
64 | let newImport = t.importDeclaration(
65 | [newImportSpecifier],
66 | t.stringLiteral(moduleName),
67 | );
68 | path.unshiftContainer('body', newImport);
69 | }
70 |
71 | return t.identifier(addedImports[exportName].name);
72 | };
73 | },
74 |
75 | VariableDeclarator(path, state) {
76 | if (path.node.id.name === '__COLOCATED_TEMPLATE__') {
77 | state.colocatedTemplateFound = true;
78 | }
79 | },
80 |
81 | ExportDefaultDeclaration(path, state) {
82 | let defaultExportDeclarationPath = path.get('declaration');
83 | let defaultExportIsExpressionOrClass =
84 | defaultExportDeclarationPath.isClass() ||
85 | defaultExportDeclarationPath.isExpression() ||
86 | defaultExportDeclarationPath.isFunction();
87 |
88 | if (
89 | state.colocatedTemplateFound !== true ||
90 | state.setComponentTemplateInjected === true ||
91 | !defaultExportIsExpressionOrClass
92 | ) {
93 | return;
94 | }
95 |
96 | state.setComponentTemplateInjected = true;
97 | let defaultExportDeclaration = path.node.declaration;
98 | let setComponentTemplateMemberExpression =
99 | makeSetComponentTemplateExpression(state);
100 | let colocatedTemplateIdentifier = makeColocatedTemplateIdentifier();
101 |
102 | if (defaultExportDeclaration.type === 'ClassDeclaration') {
103 | // when the default export is a ClassDeclaration with an `id`,
104 | // wrapping it in a CallExpression would remove that class from the
105 | // local scope which would cause issues for folks using the declared
106 | // name _after_ the export
107 | if (defaultExportDeclaration.id !== null) {
108 | path.parent.body.push(
109 | t.expressionStatement(
110 | t.callExpression(setComponentTemplateMemberExpression, [
111 | colocatedTemplateIdentifier,
112 | defaultExportDeclaration.id,
113 | ]),
114 | ),
115 | );
116 | } else {
117 | path.node.declaration = t.callExpression(
118 | setComponentTemplateMemberExpression,
119 | [
120 | colocatedTemplateIdentifier,
121 | t.classExpression(
122 | null,
123 | defaultExportDeclaration.superClass,
124 | defaultExportDeclaration.body,
125 | ),
126 | ],
127 | );
128 | }
129 | } else {
130 | path.node.declaration = t.callExpression(
131 | setComponentTemplateMemberExpression,
132 | [colocatedTemplateIdentifier, defaultExportDeclaration],
133 | );
134 | }
135 | },
136 |
137 | ExportNamedDeclaration(path, state) {
138 | if (
139 | state.colocatedTemplateFound !== true ||
140 | state.setComponentTemplateInjected === true
141 | ) {
142 | return;
143 | }
144 |
145 | let defaultSpecifier = path.node.specifiers.find(
146 | (spec) => spec.exported.name === 'default',
147 | );
148 | if (defaultSpecifier) {
149 | state.setComponentTemplateInjected = true;
150 | path.parent.body.push(
151 | t.expressionStatement(
152 | t.callExpression(makeSetComponentTemplateExpression(state), [
153 | makeColocatedTemplateIdentifier(),
154 | defaultSpecifier.local,
155 | ]),
156 | ),
157 | );
158 | }
159 | },
160 | },
161 | };
162 | };
163 |
--------------------------------------------------------------------------------
/node-tests/colocated-babel-plugin-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const babel = require('@babel/core');
4 | const ColocatedBabelPlugin = require('../lib/colocated-babel-plugin');
5 | const { expect } = require('./assertions');
6 | const DecoratorsPlugin = [
7 | require.resolve('@babel/plugin-proposal-decorators'),
8 | { legacy: true },
9 | ];
10 | const TypeScriptPlugin = [
11 | require.resolve('@babel/plugin-transform-typescript'),
12 | ];
13 | const ClassPropertiesPlugin = [
14 | require.resolve('@babel/plugin-transform-class-properties'),
15 | { loose: true },
16 | ];
17 | const RuntimePlugin = [
18 | require.resolve('@babel/plugin-transform-runtime'),
19 | {
20 | version: require('@babel/runtime/package').version,
21 | regenerator: false,
22 | useESModules: true,
23 | },
24 | ];
25 |
26 | describe('ColocatedBabelPlugin', function () {
27 | this.slow(500);
28 |
29 | it('can be used with decorators', function () {
30 | let { code } = babel.transformSync(
31 | `
32 | import Component from '@glimmer/component';
33 | const __COLOCATED_TEMPLATE__ = 'ok';
34 |
35 | export default class MyComponent extends Component {
36 | @tracked data = null;
37 | };
38 | `,
39 | {
40 | plugins: [
41 | RuntimePlugin,
42 | ColocatedBabelPlugin,
43 | DecoratorsPlugin,
44 | ClassPropertiesPlugin,
45 | ],
46 | },
47 | );
48 |
49 | expect(code).toEqualCode(
50 | `
51 | import _initializerDefineProperty from "@babel/runtime/helpers/esm/initializerDefineProperty";
52 | import _applyDecoratedDescriptor from "@babel/runtime/helpers/esm/applyDecoratedDescriptor";
53 | import _initializerWarningHelper from "@babel/runtime/helpers/esm/initializerWarningHelper";
54 |
55 | var _class, _descriptor;
56 |
57 | import { setComponentTemplate as _setComponentTemplate } from "@ember/component";
58 | import Component from '@glimmer/component';
59 | const __COLOCATED_TEMPLATE__ = 'ok';
60 | let MyComponent = (_class = class MyComponent extends Component {
61 | constructor(...args) {
62 | super(...args);
63 |
64 | _initializerDefineProperty(this, "data", _descriptor, this);
65 | }
66 |
67 | }, (_descriptor = _applyDecoratedDescriptor(_class.prototype, "data", [tracked], {
68 | configurable: true,
69 | enumerable: true,
70 | writable: true,
71 | initializer: function () {
72 | return null;
73 | }
74 | })), _class);
75 | export { MyComponent as default };
76 | ;
77 |
78 | _setComponentTemplate(__COLOCATED_TEMPLATE__, MyComponent);
79 | `,
80 | );
81 | });
82 |
83 | it('can be used with TypeScript merged declarations', function () {
84 | let { code } = babel.transformSync(
85 | `
86 | import Component from 'somewhere';
87 | const __COLOCATED_TEMPLATE__ = 'ok';
88 | type MyArgs = { required: string; optional?: number };
89 |
90 | export default interface MyComponent extends MyArgs {}
91 | export default class MyComponent extends Component {}
92 | `,
93 | { plugins: [ColocatedBabelPlugin, TypeScriptPlugin] },
94 | );
95 |
96 | expect(code).toEqualCode(
97 | `
98 | import { setComponentTemplate as _setComponentTemplate } from "@ember/component";
99 | import Component from 'somewhere';
100 | const __COLOCATED_TEMPLATE__ = 'ok';
101 | export default class MyComponent extends Component {}
102 |
103 | _setComponentTemplate(__COLOCATED_TEMPLATE__, MyComponent);
104 | `,
105 | );
106 | });
107 |
108 | it('sets the template for non-class default exports', function () {
109 | let { code } = babel.transformSync(
110 | `
111 | import MyComponent from 'other-module';
112 | const __COLOCATED_TEMPLATE__ = 'ok';
113 | export default MyComponent;
114 | `,
115 | { plugins: [ColocatedBabelPlugin] },
116 | );
117 |
118 | expect(code).toEqualCode(
119 | `
120 | import { setComponentTemplate as _setComponentTemplate } from "@ember/component";
121 | import MyComponent from 'other-module';
122 | const __COLOCATED_TEMPLATE__ = 'ok';
123 | export default _setComponentTemplate(__COLOCATED_TEMPLATE__, MyComponent);
124 | `,
125 | );
126 | });
127 |
128 | it('sets the template for named class default exports', function () {
129 | let { code } = babel.transformSync(
130 | `
131 | import Component from 'somewhere';
132 | const __COLOCATED_TEMPLATE__ = 'ok';
133 | export default class MyComponent extends Component {}
134 | `,
135 | { plugins: [ColocatedBabelPlugin] },
136 | );
137 |
138 | expect(code).toEqualCode(
139 | `
140 | import { setComponentTemplate as _setComponentTemplate } from "@ember/component";
141 | import Component from 'somewhere';
142 | const __COLOCATED_TEMPLATE__ = 'ok';
143 | export default class MyComponent extends Component {}
144 |
145 | _setComponentTemplate(__COLOCATED_TEMPLATE__, MyComponent);
146 | `,
147 | );
148 | });
149 |
150 | it('sets the template for anonymous class default exports', function () {
151 | let { code } = babel.transformSync(
152 | `
153 | import Component from 'somewhere';
154 | const __COLOCATED_TEMPLATE__ = 'ok';
155 | export default class extends Component {}
156 | `,
157 | { plugins: [ColocatedBabelPlugin] },
158 | );
159 |
160 | expect(code).toEqualCode(
161 | `
162 | import { setComponentTemplate as _setComponentTemplate } from "@ember/component";
163 | import Component from 'somewhere';
164 | const __COLOCATED_TEMPLATE__ = 'ok';
165 | export default _setComponentTemplate(__COLOCATED_TEMPLATE__, class extends Component {});
166 | `,
167 | );
168 | });
169 |
170 | it('sets the template for identifier `as default` exports', function () {
171 | let { code } = babel.transformSync(
172 | `
173 | import Component from 'somewhere';
174 | const __COLOCATED_TEMPLATE__ = 'ok';
175 | const MyComponent = class extends Component {};
176 | export { MyComponent as default };
177 | `,
178 | { plugins: [ColocatedBabelPlugin] },
179 | );
180 |
181 | expect(code).toEqualCode(
182 | `
183 | import { setComponentTemplate as _setComponentTemplate } from "@ember/component";
184 | import Component from 'somewhere';
185 | const __COLOCATED_TEMPLATE__ = 'ok';
186 | const MyComponent = class extends Component {};
187 | export { MyComponent as default };
188 |
189 | _setComponentTemplate(__COLOCATED_TEMPLATE__, MyComponent);
190 | `,
191 | );
192 | });
193 | });
194 |
--------------------------------------------------------------------------------
/lib/colocated-broccoli-plugin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const walkSync = require('walk-sync');
6 | const Plugin = require('broccoli-plugin');
7 | const logger = require('heimdalljs-logger')(
8 | 'ember-cli-htmlbars:colocated-broccoli-plugin',
9 | );
10 | const FSTree = require('fs-tree-diff');
11 |
12 | module.exports = class ColocatedTemplateProcessor extends Plugin {
13 | constructor(tree) {
14 | super([tree], {
15 | persistentOutput: true,
16 | });
17 |
18 | this._lastTree = FSTree.fromEntries([]);
19 | }
20 |
21 | calculatePatch() {
22 | let updatedEntries = walkSync.entries(this.inputPaths[0]);
23 | let currentTree = FSTree.fromEntries(updatedEntries);
24 |
25 | let patch = this._lastTree.calculatePatch(currentTree);
26 |
27 | this._lastTree = currentTree;
28 |
29 | return patch;
30 | }
31 |
32 | currentEntries() {
33 | return this._lastTree.entries;
34 | }
35 |
36 | inputHasFile(relativePath) {
37 | return !!this.currentEntries().find((e) => e.relativePath === relativePath);
38 | }
39 |
40 | detectRootName() {
41 | let entries = this.currentEntries().filter((e) => !e.isDirectory());
42 |
43 | let [first] = entries;
44 | let parts = first.relativePath.split('/');
45 |
46 | let root;
47 | if (parts[0].startsWith('@')) {
48 | root = parts.slice(0, 2).join('/');
49 | } else {
50 | root = parts[0];
51 | }
52 |
53 | if (!entries.every((e) => e.relativePath.startsWith(root))) {
54 | root = null;
55 | }
56 |
57 | return root;
58 | }
59 |
60 | build() {
61 | let patch = this.calculatePatch();
62 |
63 | // We skip building if this is a rebuild with a zero-length patch
64 | if (patch.length === 0) {
65 | return;
66 | }
67 |
68 | let root = this.detectRootName();
69 |
70 | let processedColocatedFiles = new Set();
71 |
72 | for (let operation of patch) {
73 | let [method, relativePath] = operation;
74 |
75 | let filePathParts = path.parse(relativePath);
76 |
77 | let isOutsideComponentsFolder = !relativePath.startsWith(
78 | `${root}/components/`,
79 | );
80 | let isPodsTemplate =
81 | filePathParts.name === 'template' && filePathParts.ext === '.hbs';
82 | let isNotColocationExtension = ![
83 | '.hbs',
84 | '.js',
85 | '.ts',
86 | '.coffee',
87 | ].includes(filePathParts.ext);
88 | let isDirectoryOperation = ['rmdir', 'mkdir'].includes(method);
89 | let basePath = path.posix.join(filePathParts.dir, filePathParts.name);
90 | let relativeTemplatePath = basePath + '.hbs';
91 |
92 | // if the change in question has nothing to do with colocated templates
93 | // just apply the patch to the outputPath
94 | if (
95 | isOutsideComponentsFolder ||
96 | isPodsTemplate ||
97 | isNotColocationExtension ||
98 | isDirectoryOperation
99 | ) {
100 | logger.debug(
101 | `default operation for non-colocation modification: ${relativePath}`,
102 | );
103 | FSTree.applyPatch(this.inputPaths[0], this.outputPath, [operation]);
104 | continue;
105 | }
106 |
107 | // we have already processed this colocated file, carry on
108 | if (processedColocatedFiles.has(basePath)) {
109 | continue;
110 | }
111 | processedColocatedFiles.add(basePath);
112 |
113 | let hasBackingClass = false;
114 | let hasTemplate = this.inputHasFile(basePath + '.hbs');
115 | let backingClassPath = basePath;
116 |
117 | if (this.inputHasFile(basePath + '.js')) {
118 | backingClassPath += '.js';
119 | hasBackingClass = true;
120 | } else if (this.inputHasFile(basePath + '.ts')) {
121 | backingClassPath += '.ts';
122 | hasBackingClass = true;
123 | } else if (this.inputHasFile(basePath + '.coffee')) {
124 | backingClassPath += '.coffee';
125 | hasBackingClass = true;
126 | } else {
127 | backingClassPath += '.js';
128 | hasBackingClass = false;
129 | }
130 |
131 | let originalJsContents = null;
132 | let jsContents = null;
133 | let prefix = '';
134 |
135 | if (hasTemplate) {
136 | let templatePath = path.join(this.inputPaths[0], basePath + '.hbs');
137 | let templateContents = fs.readFileSync(templatePath, {
138 | encoding: 'utf8',
139 | });
140 | let hbsInvocationOptions = {
141 | contents: templateContents,
142 | moduleName: relativeTemplatePath,
143 | parseOptions: {
144 | srcName: relativeTemplatePath,
145 | },
146 | };
147 | let hbsInvocation = `hbs(${JSON.stringify(
148 | templateContents,
149 | )}, ${JSON.stringify(hbsInvocationOptions)})`;
150 |
151 | prefix = `import { hbs } from 'ember-cli-htmlbars';\nconst __COLOCATED_TEMPLATE__ = ${hbsInvocation};\n`;
152 | if (backingClassPath.endsWith('.coffee')) {
153 | prefix = `import { hbs } from 'ember-cli-htmlbars'\n__COLOCATED_TEMPLATE__ = ${hbsInvocation}\n`;
154 | }
155 | }
156 |
157 | if (hasBackingClass) {
158 | // add the template, call setComponentTemplate
159 |
160 | jsContents = originalJsContents = fs.readFileSync(
161 | path.join(this.inputPaths[0], backingClassPath),
162 | {
163 | encoding: 'utf8',
164 | },
165 | );
166 |
167 | if (hasTemplate && jsContents.includes('export { default }')) {
168 | let message = `\`${backingClassPath}\` contains an \`export { default }\` re-export, but it has a co-located template. You must explicitly extend the component to assign it a different template.`;
169 | jsContents = `${jsContents}\nthrow new Error(${JSON.stringify(
170 | message,
171 | )});`;
172 | prefix = '';
173 | } else if (hasTemplate && !jsContents.includes('export default')) {
174 | let message = `\`${backingClassPath}\` does not contain a \`default export\`. Did you forget to export the component class?`;
175 | jsContents = `${jsContents}\nthrow new Error(${JSON.stringify(
176 | message,
177 | )});`;
178 | prefix = '';
179 | }
180 | } else {
181 | // create JS file, use null component pattern
182 |
183 | jsContents = `import templateOnly from '@ember/component/template-only';\n\nexport default templateOnly();\n`;
184 | }
185 |
186 | jsContents = prefix + jsContents;
187 |
188 | let jsOutputPath = path.join(this.outputPath, backingClassPath);
189 |
190 | switch (method) {
191 | case 'unlink': {
192 | if (filePathParts.ext === '.hbs' && hasBackingClass) {
193 | fs.writeFileSync(jsOutputPath, originalJsContents, {
194 | encoding: 'utf8',
195 | });
196 |
197 | logger.debug(`removing colocated template for: ${basePath}`);
198 | } else if (filePathParts.ext !== '.hbs' && hasTemplate) {
199 | fs.writeFileSync(jsOutputPath, jsContents, { encoding: 'utf8' });
200 | logger.debug(
201 | `converting colocated template with backing class to template only: ${basePath}`,
202 | );
203 | } else {
204 | // Copied from https://github.com/stefanpenner/fs-tree-diff/blob/v2.0.1/lib/index.ts#L38-L68
205 | try {
206 | fs.unlinkSync(jsOutputPath);
207 | } catch (e) {
208 | if (typeof e === 'object' && e !== null && e.code === 'ENOENT') {
209 | return;
210 | }
211 | throw e;
212 | }
213 | }
214 | break;
215 | }
216 | case 'change':
217 | case 'create': {
218 | fs.writeFileSync(jsOutputPath, jsContents, { encoding: 'utf8' });
219 |
220 | logger.debug(
221 | `writing colocated template: ${basePath} (template-only: ${!hasBackingClass})`,
222 | );
223 | break;
224 | }
225 | default: {
226 | throw new Error(
227 | `ember-cli-htmlbars: Unexpected operation when patching files for colocation.\n\tOperation:\n${JSON.stringify(
228 | [method, relativePath],
229 | )}\n\tKnown files:\n${JSON.stringify(
230 | this.currentEntries().map((e) => e.relativePath),
231 | null,
232 | 2,
233 | )}`,
234 | );
235 | }
236 | }
237 | }
238 | }
239 | };
240 |
--------------------------------------------------------------------------------
/node-tests/colocated-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ColocatedTemplateCompiler = require('../lib/colocated-broccoli-plugin');
4 | const ColocatedBabelPlugin = require.resolve('../lib/colocated-babel-plugin');
5 | const BroccoliPersistentFilter = require('broccoli-persistent-filter');
6 | const babel = require('@babel/core');
7 | const TypescriptTransform = require.resolve(
8 | '@babel/plugin-transform-typescript',
9 | );
10 | const {
11 | createTempDir,
12 | createBuilder: _createBuilder,
13 | } = require('broccoli-test-helper');
14 | const { expect } = require('./assertions');
15 |
16 | // this is silly, we should use broccoli-babel-transpiler but it has **very** bad behaviors
17 | // when used inside a process that does not end with `process.exit` (e.g. this test suite)
18 | // See https://github.com/babel/broccoli-babel-transpiler/issues/169 for details.
19 | class BabelTranspiler extends BroccoliPersistentFilter {
20 | constructor(input, options = {}) {
21 | options.async = true;
22 | options.concurrency = 1;
23 |
24 | super(input, options);
25 |
26 | this.extensions = ['js', 'ts'];
27 | this.targetExtension = 'js';
28 |
29 | this.options = options;
30 | }
31 |
32 | processString(string) {
33 | let { code } = babel.transformSync(string, {
34 | plugins: this.options.plugins,
35 | });
36 |
37 | return code;
38 | }
39 | }
40 |
41 | describe('Colocation - Broccoli + Babel Integration', function () {
42 | this.timeout(10000);
43 |
44 | let input, output;
45 |
46 | beforeEach(async function () {
47 | input = await createTempDir();
48 | });
49 |
50 | afterEach(async function () {
51 | await input.dispose();
52 |
53 | if (output) {
54 | await output.dispose();
55 | }
56 | });
57 |
58 | function createBuilder(plugins = []) {
59 | let colocatedTree = new ColocatedTemplateCompiler(input.path(), {
60 | precompile(template) {
61 | return JSON.stringify({ template });
62 | },
63 | });
64 |
65 | let babelTree = new BabelTranspiler(colocatedTree, {
66 | plugins: [...plugins, ColocatedBabelPlugin],
67 | });
68 |
69 | output = _createBuilder(babelTree);
70 |
71 | return output;
72 | }
73 |
74 | it('works for template only component', async function () {
75 | input.write({
76 | 'app-name-here': {
77 | components: {
78 | 'foo.hbs': `{{yield}}`,
79 | },
80 | templates: {
81 | 'application.hbs': `{{outlet}}`,
82 | },
83 | },
84 | });
85 |
86 | createBuilder();
87 |
88 | await output.build();
89 |
90 | expect(output.read()).toDeepEqualCode({
91 | 'app-name-here': {
92 | components: {
93 | 'foo.js': `
94 | import { setComponentTemplate as _setComponentTemplate } from "@ember/component";
95 | import { hbs } from 'ember-cli-htmlbars';
96 |
97 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {
98 | "contents": "{{yield}}",
99 | "moduleName": "app-name-here/components/foo.hbs",
100 | "parseOptions": {
101 | "srcName": "app-name-here/components/foo.hbs"
102 | }
103 | });
104 |
105 | import templateOnly from '@ember/component/template-only';
106 | export default _setComponentTemplate(__COLOCATED_TEMPLATE__, templateOnly());
107 | `,
108 | },
109 | templates: {
110 | 'application.hbs': '{{outlet}}',
111 | },
112 | },
113 | });
114 | });
115 |
116 | it('works for component with template and class', async function () {
117 | input.write({
118 | 'app-name-here': {
119 | components: {
120 | 'foo.hbs': `{{yield}}`,
121 | 'foo.js': `
122 | import Component from '@glimmer/component';
123 |
124 | export default class FooComponent extends Component {}
125 | `,
126 | },
127 | templates: {
128 | 'application.hbs': `{{outlet}}`,
129 | },
130 | },
131 | });
132 |
133 | createBuilder();
134 |
135 | await output.build();
136 |
137 | expect(output.read()).toDeepEqualCode({
138 | 'app-name-here': {
139 | components: {
140 | 'foo.js': `
141 | import { setComponentTemplate as _setComponentTemplate } from "@ember/component";
142 | import { hbs } from 'ember-cli-htmlbars';
143 |
144 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {
145 | "contents": "{{yield}}",
146 | "moduleName": "app-name-here/components/foo.hbs",
147 | "parseOptions": {
148 | "srcName": "app-name-here/components/foo.hbs"
149 | }
150 | });
151 |
152 | import Component from '@glimmer/component';
153 | export default class FooComponent extends Component {}
154 |
155 | _setComponentTemplate(__COLOCATED_TEMPLATE__, FooComponent);
156 | `,
157 | },
158 | templates: {
159 | 'application.hbs': '{{outlet}}',
160 | },
161 | },
162 | });
163 | });
164 |
165 | it('works for typescript component class with template', async function () {
166 | input.write({
167 | 'app-name-here': {
168 | components: {
169 | 'foo.hbs': `{{yield}}`,
170 | 'foo.ts': `
171 | import Component from '@glimmer/component';
172 |
173 | export default class FooComponent extends Component {}
174 | `,
175 | },
176 | templates: {
177 | 'application.hbs': `{{outlet}}`,
178 | },
179 | },
180 | });
181 |
182 | createBuilder([TypescriptTransform]);
183 |
184 | await output.build();
185 |
186 | expect(output.read()).toDeepEqualCode({
187 | 'app-name-here': {
188 | components: {
189 | 'foo.js': `
190 | import { setComponentTemplate as _setComponentTemplate } from "@ember/component";
191 | import { hbs } from 'ember-cli-htmlbars';
192 |
193 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {
194 | "contents": "{{yield}}",
195 | "moduleName": "app-name-here/components/foo.hbs",
196 | "parseOptions": {
197 | "srcName": "app-name-here/components/foo.hbs"
198 | }
199 | });
200 |
201 | import Component from '@glimmer/component';
202 | export default class FooComponent extends Component {}
203 |
204 | _setComponentTemplate(__COLOCATED_TEMPLATE__, FooComponent);
205 | `,
206 | },
207 | templates: {
208 | 'application.hbs': '{{outlet}}',
209 | },
210 | },
211 | });
212 | });
213 |
214 | it('works for scoped addon using template only component', async function () {
215 | input.write({
216 | '@scope-name': {
217 | 'addon-name-here': {
218 | components: {
219 | 'foo.hbs': `{{yield}}`,
220 | },
221 | templates: {
222 | 'application.hbs': `{{outlet}}`,
223 | },
224 | },
225 | },
226 | });
227 |
228 | createBuilder();
229 |
230 | await output.build();
231 |
232 | expect(output.read()).toDeepEqualCode({
233 | '@scope-name': {
234 | 'addon-name-here': {
235 | components: {
236 | 'foo.js': `
237 | import { setComponentTemplate as _setComponentTemplate } from "@ember/component";
238 | import { hbs } from 'ember-cli-htmlbars';
239 |
240 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {
241 | "contents": "{{yield}}",
242 | "moduleName": "@scope-name/addon-name-here/components/foo.hbs",
243 | "parseOptions": {
244 | "srcName": "@scope-name/addon-name-here/components/foo.hbs"
245 | }
246 | });
247 |
248 | import templateOnly from '@ember/component/template-only';
249 | export default _setComponentTemplate(__COLOCATED_TEMPLATE__, templateOnly());
250 | `,
251 | },
252 | templates: {
253 | 'application.hbs': '{{outlet}}',
254 | },
255 | },
256 | },
257 | });
258 | });
259 |
260 | it('works for scoped addon using component with template and class', async function () {
261 | input.write({
262 | '@scope-name': {
263 | 'addon-name-here': {
264 | components: {
265 | 'foo.hbs': `{{yield}}`,
266 | 'foo.js': `
267 | import Component from '@glimmer/component';
268 | export default class FooComponent extends Component {}
269 | `,
270 | },
271 | templates: {
272 | 'application.hbs': `{{outlet}}`,
273 | },
274 | },
275 | },
276 | });
277 |
278 | createBuilder();
279 |
280 | await output.build();
281 |
282 | expect(output.read()).toDeepEqualCode({
283 | '@scope-name': {
284 | 'addon-name-here': {
285 | components: {
286 | 'foo.js': `
287 | import { setComponentTemplate as _setComponentTemplate } from "@ember/component";
288 | import { hbs } from 'ember-cli-htmlbars';
289 |
290 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {
291 | "contents": "{{yield}}",
292 | "moduleName": "@scope-name/addon-name-here/components/foo.hbs",
293 | "parseOptions": {
294 | "srcName": "@scope-name/addon-name-here/components/foo.hbs"
295 | }
296 | });
297 |
298 | import Component from '@glimmer/component';
299 | export default class FooComponent extends Component {}
300 |
301 | _setComponentTemplate(__COLOCATED_TEMPLATE__, FooComponent);
302 | `,
303 | },
304 | templates: {
305 | 'application.hbs': '{{outlet}}',
306 | },
307 | },
308 | },
309 | });
310 | });
311 |
312 | it('does nothing for "classic" location components', async function () {
313 | input.write({
314 | 'app-name-here': {
315 | components: {
316 | 'foo.js': `
317 | import Component from '@glimmer/component';
318 | export default class FooComponent extends Component {}
319 | `,
320 | },
321 | templates: {
322 | 'application.hbs': `{{outlet}}`,
323 | components: {
324 | 'foo.hbs': `{{yield}}`,
325 | },
326 | },
327 | },
328 | });
329 |
330 | createBuilder();
331 |
332 | await output.build();
333 |
334 | expect(output.read()).toDeepEqualCode(input.read());
335 | });
336 |
337 | it('does nothing for "pod" location templates', async function () {
338 | input.write({
339 | 'addon-name-here': {
340 | components: {
341 | foo: {
342 | 'template.hbs': `{{yield}}`,
343 | },
344 | },
345 | },
346 | });
347 |
348 | createBuilder();
349 | await output.build();
350 |
351 | expect(output.read()).toDeepEqualCode(input.read());
352 | });
353 |
354 | it('it works if there are no input files', async function () {
355 | input.write({});
356 |
357 | createBuilder();
358 | await output.build();
359 |
360 | expect(output.read()).toDeepEqualCode({});
361 | });
362 |
363 | it('it works if input is manually using setComponentTemplate - no colocated template exists', async function () {
364 | input.write({
365 | 'app-name-here': {
366 | components: {
367 | 'foo.js': `
368 | import Component from '@glimmer/component';
369 | import { setComponentTemplate } from '@ember/component';
370 | import hbs from 'ember-cli-htmlbars-inline-precompile';
371 | export default class FooComponent extends Component {}
372 | setComponentTemplate(FooComponent, hbs\`sometemplate\`);
373 | `,
374 | },
375 | templates: {
376 | 'application.hbs': `{{outlet}}`,
377 | },
378 | },
379 | });
380 |
381 | createBuilder();
382 | await output.build();
383 |
384 | expect(output.read()).toDeepEqualCode({
385 | 'app-name-here': {
386 | components: {
387 | 'foo.js': `
388 | import Component from '@glimmer/component';
389 | import { setComponentTemplate } from '@ember/component';
390 | import hbs from 'ember-cli-htmlbars-inline-precompile';
391 | export default class FooComponent extends Component {}
392 | setComponentTemplate(FooComponent, hbs\`sometemplate\`);
393 | `,
394 | },
395 | templates: {
396 | 'application.hbs': '{{outlet}}',
397 | },
398 | },
399 | });
400 | });
401 |
402 | it('emits an error when a default export is not present in a component JS file', async function () {
403 | input.write({
404 | 'app-name-here': {
405 | components: {
406 | 'foo.hbs': `{{yield}}`,
407 | 'foo.js': `
408 | export function whatever() {}
409 | `,
410 | },
411 | },
412 | });
413 |
414 | createBuilder();
415 | await output.build();
416 |
417 | expect(output.read()).toDeepEqualCode({
418 | 'app-name-here': {
419 | components: {
420 | 'foo.js': `
421 | export function whatever() {}\nthrow new Error("\`app-name-here/components/foo.js\` does not contain a \`default export\`. Did you forget to export the component class?");
422 | `,
423 | },
424 | },
425 | });
426 | });
427 |
428 | it('does not break class decorator usage');
429 | });
430 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ## v7.0.0 (2025-11-25)
10 |
11 | #### :boom: Breaking Change
12 | * [#788](https://github.com/ember-cli/ember-cli-htmlbars/pull/788) update minimum node version ([@ef4](https://github.com/ef4))
13 | * [#786](https://github.com/ember-cli/ember-cli-htmlbars/pull/786) big cleanup ([@ef4](https://github.com/ef4))
14 | * [#785](https://github.com/ember-cli/ember-cli-htmlbars/pull/785) Drop support for node < 18 and ember < 4.12 ([@ef4](https://github.com/ef4))
15 |
16 | #### :house: Internal
17 | * [#775](https://github.com/ember-cli/ember-cli-htmlbars/pull/775) Fix CI ([@bertdeblock](https://github.com/bertdeblock))
18 |
19 | #### Committers: 2
20 | - Bert De Block ([@bertdeblock](https://github.com/bertdeblock))
21 | - Edward Faulkner ([@ef4](https://github.com/ef4))
22 |
23 | ## v6.3.0 (2023-08-08)
24 |
25 | #### :rocket: Enhancement
26 | * [#773](https://github.com/ember-cli/ember-cli-htmlbars/pull/773) Adjust error message for re-exported class when it has a co-located template ([@robinborst95](https://github.com/robinborst95))
27 |
28 | #### :bug: Bug Fix
29 | * [#764](https://github.com/ember-cli/ember-cli-htmlbars/pull/764) Export non-user-constructible `TemplateFactory` type ([@chriskrycho](https://github.com/chriskrycho))
30 |
31 | #### Committers: 2
32 | - Chris Krycho ([@chriskrycho](https://github.com/chriskrycho))
33 | - Robin Borst ([@robinborst95](https://github.com/robinborst95))
34 |
35 |
36 | ## v6.2.0 (2023-01-17)
37 |
38 | #### :rocket: Enhancement
39 | * [#762](https://github.com/ember-cli/ember-cli-htmlbars/pull/762) Upgrade to `babel-plugin-ember-template-compilation` v2 ([@dfreeman](https://github.com/dfreeman))
40 |
41 | #### Committers: 1
42 | - Dan Freeman ([@dfreeman](https://github.com/dfreeman))
43 |
44 |
45 | ## v6.1.1 (2022-09-08)
46 |
47 | #### :bug: Bug Fix
48 | * [#755](https://github.com/ember-cli/ember-cli-htmlbars/pull/755) Fix template compilation in addons on ember-cli < 3.13. This was a regression in 6.1.0. ([@mansona](https://github.com/mansona))
49 |
50 | #### Committers: 1
51 | - Chris Manson ([@mansona](https://github.com/mansona))
52 |
53 | ## v6.1.0 (2022-07-04)
54 |
55 | #### :rocket: Enhancement
56 | * [#749](https://github.com/ember-cli/ember-cli-htmlbars/pull/749) Drive all template compilation from babel ([@ef4](https://github.com/ef4))
57 |
58 | #### :bug: Bug Fix
59 | * [#747](https://github.com/ember-cli/ember-cli-htmlbars/pull/747) Avoid registering `babel-plugin-ember-template-compilation` repeatedly ([@dfreeman](https://github.com/dfreeman))
60 | * [#741](https://github.com/ember-cli/ember-cli-htmlbars/pull/741) Fix incorrect ember-source version check ([@simonihmig](https://github.com/simonihmig))
61 |
62 | #### :memo: Documentation
63 | * [#743](https://github.com/ember-cli/ember-cli-htmlbars/pull/743) Fix lin to ASTPluginBuilder type ([@mehulkar](https://github.com/mehulkar))
64 |
65 | #### Committers: 3
66 | - Edward Faulkner ([@ef4](https://github.com/ef4))
67 | - Dan Freeman ([@dfreeman](https://github.com/dfreeman))
68 | - Mehul Kar ([@mehulkar](https://github.com/mehulkar))
69 | - Simon Ihmig ([@simonihmig](https://github.com/simonihmig))
70 |
71 | ## v6.0.1 (2021-12-05)
72 |
73 | #### :bug: Bug Fix
74 | * [#739](https://github.com/ember-cli/ember-cli-htmlbars/pull/739) Allow console messages (v6.x, master) ([@mixonic](https://github.com/mixonic))
75 |
76 | #### Committers: 1
77 | - Matthew Beale ([@mixonic](https://github.com/mixonic))
78 |
79 |
80 | ## v6.0.0 (2021-10-14)
81 |
82 | #### :boom: Breaking Change
83 | * [#724](https://github.com/ember-cli/ember-cli-htmlbars/pull/724) Use simplified babel plugin on ember 3.27+ and drop unsupported node versions ([@ef4](https://github.com/ef4))
84 |
85 | #### :rocket: Enhancement
86 | * [#733](https://github.com/ember-cli/ember-cli-htmlbars/pull/733) Avoid repeated encoding in getTemplateCompiler ([@rwjblue](https://github.com/rwjblue))
87 | * [#724](https://github.com/ember-cli/ember-cli-htmlbars/pull/724) Use simplified babel plugin on ember 3.27+ and drop unsupported node versions ([@ef4](https://github.com/ef4))
88 |
89 | #### :bug: Bug Fix
90 | * [#732](https://github.com/ember-cli/ember-cli-htmlbars/pull/732) add `this._super` call to included hook plugin doc ([@fivetanley](https://github.com/fivetanley))
91 |
92 | #### :house: Internal
93 | * [#736](https://github.com/ember-cli/ember-cli-htmlbars/pull/736) Updating pr 723 ([@ef4](https://github.com/ef4))
94 |
95 | #### Committers: 3
96 | - Edward Faulkner ([@ef4](https://github.com/ef4))
97 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
98 | - Stanley Stuart ([@fivetanley](https://github.com/fivetanley))
99 |
100 |
101 | ## v5.7.1 (2021-03-18)
102 |
103 | #### :bug: Bug Fix
104 | * [#685](https://github.com/ember-cli/ember-cli-htmlbars/pull/685) Ensure global is present for Node 10 + globalThis polyfill ([@rwjblue](https://github.com/rwjblue))
105 |
106 | #### Committers: 1
107 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
108 |
109 |
110 | ## v5.7.0 (2021-03-18)
111 |
112 | #### :rocket: Enhancement
113 | * [#683](https://github.com/ember-cli/ember-cli-htmlbars/pull/683) Disable the modules API polyfill on Ember 3.27+ ([@pzuraq](https://github.com/pzuraq))
114 |
115 | #### :house: Internal
116 | * [#684](https://github.com/ember-cli/ember-cli-htmlbars/pull/684) Update babel-plugin-htmlbars-inline-precompile to 4.4.6. ([@rwjblue](https://github.com/rwjblue))
117 |
118 | #### Committers: 2
119 | - Chris Garrett ([@pzuraq](https://github.com/pzuraq))
120 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
121 |
122 |
123 | ## v5.6.5 (2021-03-12)
124 |
125 | #### :bug: Bug Fix
126 | * [#680](https://github.com/ember-cli/ember-cli-htmlbars/pull/680) Update inline template compilation plugin to avoid errors on rebuilds ([@rwjblue](https://github.com/rwjblue))
127 |
128 | #### Committers: 2
129 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
130 |
131 |
132 | ## v5.6.4 (2021-03-07)
133 |
134 | #### :bug: Bug Fix
135 | * [#678](https://github.com/ember-cli/ember-cli-htmlbars/pull/678) Make `setTimeout`/`clearTimeout` available to the template compiler sandbox ([@rwjblue](https://github.com/rwjblue))
136 | * [#677](https://github.com/ember-cli/ember-cli-htmlbars/pull/677) Support TypeScript merging of export default declarations in template colocation ([@dfreeman](https://github.com/dfreeman))
137 |
138 | #### Committers: 2
139 | - Dan Freeman ([@dfreeman](https://github.com/dfreeman))
140 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
141 |
142 |
143 | ## v5.6.3 (2021-03-04)
144 |
145 | #### :bug: Bug Fix
146 | * [#675](https://github.com/ember-cli/ember-cli-htmlbars/pull/675) Remove development only `optionalDependencies` (`release-it` and `release-it-lerna-changelog`). ([@alexlafroscia](https://github.com/alexlafroscia))
147 |
148 | #### Committers: 1
149 | - Alex LaFroscia ([@alexlafroscia](https://github.com/alexlafroscia))
150 |
151 |
152 | ## v5.6.2 (2021-02-27)
153 |
154 | #### :bug: Bug Fix
155 | * [#665](https://github.com/ember-cli/ember-cli-htmlbars/pull/665) Ensure AST plugins have the same ordering as < ember-cli-htmlbars@5.5.0. ([@rwjblue](https://github.com/rwjblue))
156 |
157 | #### Committers: 1
158 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
159 |
160 |
161 | ## v5.6.1 (2021-02-26)
162 |
163 | #### :bug: Bug Fix
164 | * [#663](https://github.com/ember-cli/ember-cli-htmlbars/pull/663) Ember Ember 3.27+ can determine global for template compilation ([@rwjblue](https://github.com/rwjblue))
165 |
166 | #### Committers: 1
167 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
168 |
169 |
170 | ## v5.6.0 (2021-02-26)
171 |
172 | #### :rocket: Enhancement
173 | * [#661](https://github.com/ember-cli/ember-cli-htmlbars/pull/661) Remove usage of registerPlugin / unregisterPlugin ([@rwjblue](https://github.com/rwjblue))
174 |
175 | #### :bug: Bug Fix
176 | * [#662](https://github.com/ember-cli/ember-cli-htmlbars/pull/662) Avoid building the template compiler cache key repeatedly ([@rwjblue](https://github.com/rwjblue))
177 |
178 | #### Committers: 1
179 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
180 |
181 |
182 | ## v5.5.0 (2021-02-26)
183 |
184 | #### :rocket: Enhancement
185 | * [#660](https://github.com/ember-cli/ember-cli-htmlbars/pull/660) Replace `purgeModule` cache busting with `vm` based sandboxing ([@rwjblue](https://github.com/rwjblue))
186 |
187 | #### Committers: 1
188 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
189 |
190 |
191 | ## v5.4.0 (2021-02-24)
192 |
193 | #### :house: Internal
194 | * [#659](https://github.com/ember-cli/ember-cli-htmlbars/pull/659) Enable experimentation via `ember-template-imports` addon ([@pzuraq](https://github.com/pzuraq))
195 |
196 | #### Committers: 1
197 | - Chris Garrett ([@pzuraq](https://github.com/pzuraq))
198 |
199 |
200 | ## v5.3.2 (2021-02-10)
201 |
202 | #### :rocket: Enhancement
203 | * [#657](https://github.com/ember-cli/ember-cli-htmlbars/pull/657) Make cacheKey lazy ([@krisselden](https://github.com/krisselden))
204 |
205 | #### Committers: 2
206 | - Kris Selden ([@krisselden](https://github.com/krisselden))
207 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
208 |
209 |
210 | ## v5.3.1 (2020-08-11)
211 |
212 | #### :bug: Bug Fix
213 | * [#599](https://github.com/ember-cli/ember-cli-htmlbars/pull/599) Move `ember-template-lint` to `devDependencies` (from `dependencies`) ([@jamescdavis](https://github.com/jamescdavis))
214 |
215 | #### Committers: 1
216 | - James C. Davis ([@jamescdavis](https://github.com/jamescdavis))
217 |
218 | ## v5.3.0 (2020-08-10)
219 |
220 | #### :rocket: Enhancement
221 | * [#597](https://github.com/ember-cli/ember-cli-htmlbars/pull/597) Pass `isProduction` to Ember template compiler. ([@rwjblue](https://github.com/rwjblue))
222 |
223 | #### :memo: Documentation
224 | * [#585](https://github.com/ember-cli/ember-cli-htmlbars/pull/585) Refactor README ([@rwjblue](https://github.com/rwjblue))
225 |
226 | #### :house: Internal
227 | * [#584](https://github.com/ember-cli/ember-cli-htmlbars/pull/584) Replace `ember-cli-template-lint` with `ember-template-lint` ([@arthirm](https://github.com/arthirm))
228 |
229 | #### Committers: 2
230 | - Arthi ([@arthirm](https://github.com/arthirm))
231 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
232 |
233 |
234 | ## v5.2.0 (2020-06-25)
235 |
236 | #### :rocket: Enhancement
237 | * [#527](https://github.com/ember-cli/ember-cli-htmlbars/pull/527) Move template compiler creation to a method on the addon ([@chriseppstein](https://github.com/chriseppstein))
238 |
239 | #### Committers: 1
240 | - Chris Eppstein ([@chriseppstein](https://github.com/chriseppstein))
241 |
242 |
243 | ## v5.1.2 (2020-05-08)
244 |
245 | #### :bug: Bug Fix
246 | * [#553](https://github.com/ember-cli/ember-cli-htmlbars/pull/553) Ensure custom templateCompilerPath is an absolute path. ([@rwjblue](https://github.com/rwjblue))
247 |
248 | #### Committers: 1
249 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
250 |
251 |
252 | ## v5.1.1 (2020-05-07)
253 |
254 | #### :bug: Bug Fix
255 | * [#551](https://github.com/ember-cli/ember-cli-htmlbars/pull/551) Ensure `EmberENV` is available to inline template compilation ([@rwjblue](https://github.com/rwjblue))
256 | * [#550](https://github.com/ember-cli/ember-cli-htmlbars/pull/550) Fix specifying custom template compiler path. ([@rwjblue](https://github.com/rwjblue))
257 |
258 | #### :house: Internal
259 | * [#547](https://github.com/ember-cli/ember-cli-htmlbars/pull/547) Add some more helpful debug logging to list AST plugins ([@rwjblue](https://github.com/rwjblue))
260 | * [#544](https://github.com/ember-cli/ember-cli-htmlbars/pull/544) Add Node 14 to CI ([@rwjblue](https://github.com/rwjblue))
261 |
262 | #### Committers: 1
263 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
264 |
265 |
266 | ## v5.1.0 (2020-05-06)
267 |
268 | #### :rocket: Enhancement
269 | * [#543](https://github.com/ember-cli/ember-cli-htmlbars/pull/543) Update babel-plugin-htmlbars-inline-precompile to 4.0.0. ([@rwjblue](https://github.com/rwjblue))
270 |
271 | #### Committers: 1
272 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
273 |
274 |
275 | ## v5.0.0 (2020-05-04)
276 |
277 | #### :boom: Breaking Change
278 | * [#496](https://github.com/ember-cli/ember-cli-htmlbars/pull/496) Drop support for Ember < 3.8. ([@rwjblue](https://github.com/rwjblue))
279 | * [#493](https://github.com/ember-cli/ember-cli-htmlbars/pull/493) Drop Node 8 support. ([@rwjblue](https://github.com/rwjblue))
280 | * [#492](https://github.com/ember-cli/ember-cli-htmlbars/pull/492) Remove Bower support. ([@rwjblue](https://github.com/rwjblue))
281 |
282 | #### :rocket: Enhancement
283 | * [#528](https://github.com/ember-cli/ember-cli-htmlbars/pull/528) Use smaller cache key for `ember-template-compiler` (reduce overall memory overhead of caching) ([@xg-wang](https://github.com/xg-wang))
284 | * [#512](https://github.com/ember-cli/ember-cli-htmlbars/pull/512) Update Broccoli dependencies to latest. ([@rwjblue](https://github.com/rwjblue))
285 |
286 | #### :house: Internal
287 | * [#514](https://github.com/ember-cli/ember-cli-htmlbars/pull/514) Update fixturify and qunit-dom to latest. ([@rwjblue](https://github.com/rwjblue))
288 | * [#513](https://github.com/ember-cli/ember-cli-htmlbars/pull/513) Update semver to 7.1.2. ([@rwjblue](https://github.com/rwjblue))
289 | * [#508](https://github.com/ember-cli/ember-cli-htmlbars/pull/508) Update to prettier@2. ([@rwjblue](https://github.com/rwjblue))
290 | * [#507](https://github.com/ember-cli/ember-cli-htmlbars/pull/507) Update Babel related devDependencies. ([@rwjblue](https://github.com/rwjblue))
291 |
292 | #### Committers: 2
293 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
294 | - Thomas Wang ([@xg-wang](https://github.com/xg-wang))
295 |
296 |
297 | ## v4.3.1 (2020-04-09)
298 |
299 | #### :bug: Bug Fix
300 | * [#494](https://github.com/ember-cli/ember-cli-htmlbars/pull/494) Ensure types file gets published. ([@rwjblue](https://github.com/rwjblue))
301 |
302 | #### Committers: 1
303 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
304 |
305 | ## v4.3.0 (2020-04-08)
306 |
307 | #### :memo: Documentation
308 | * [#481](https://github.com/ember-cli/ember-cli-htmlbars/pull/481) Add type definition for test helper import ([@chriskrycho](https://github.com/chriskrycho))
309 |
310 | #### Committers: 1
311 | - Chris Krycho ([@chriskrycho](https://github.com/chriskrycho))
312 |
313 | ## v4.2.3 (2020-02-24)
314 |
315 | #### :house: Internal
316 | * [#464](https://github.com/ember-cli/ember-cli-htmlbars/pull/464) Remove usage of legacy `checker.forEmber` API. ([@rwjblue](https://github.com/rwjblue))
317 | * [#463](https://github.com/ember-cli/ember-cli-htmlbars/pull/463) fix: Standardize the option name for dependency invalidation. ([@chriseppstein](https://github.com/chriseppstein))
318 |
319 | #### Committers: 2
320 | - Chris Eppstein ([@chriseppstein](https://github.com/chriseppstein))
321 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
322 |
323 | ## v4.2.2 (2020-01-15)
324 |
325 | #### :bug: Bug Fix
326 | * [#437](https://github.com/ember-cli/ember-cli-htmlbars/pull/437) Revert "Bump semver from 6.3.0 to 7.0.0" ([@rwjblue](https://github.com/rwjblue))
327 |
328 | #### :memo: Documentation
329 | * [#425](https://github.com/ember-cli/ember-cli-htmlbars/pull/425) Changelog: Fix wrong version ([@Turbo87](https://github.com/Turbo87))
330 |
331 | #### Committers: 3
332 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
333 | - Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))
334 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
335 |
336 | ## v4.2.1 (2020-01-09)
337 |
338 | #### :bug: Bug Fix
339 | * [#423](https://github.com/ember-cli/ember-cli-htmlbars/pull/423) Only check semver range if ember-source is present ([@mixonic](https://github.com/mixonic))
340 | * [#392](https://github.com/ember-cli/ember-cli-htmlbars/pull/392) Ensure inline precompilation does not error when a template contains `*/` ([@rwjblue](https://github.com/rwjblue))
341 |
342 | #### Committers: 2
343 | - Matthew Beale ([@mixonic](https://github.com/mixonic))
344 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
345 |
346 | ## v4.2.0 (2019-12-11)
347 |
348 | #### :rocket: Enhancement
349 | * [#384](https://github.com/ember-cli/ember-cli-htmlbars/pull/384) Remove `setEdition` requirement for colocation. ([@rwjblue](https://github.com/rwjblue))
350 |
351 | #### Committers: 1
352 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
353 |
354 | ## v4.1.1 (2019-12-11)
355 |
356 | #### :bug: Bug Fix
357 | * [#390](https://github.com/ember-cli/ember-cli-htmlbars/pull/390) Ensure reexported components do not throw an error. ([@rwjblue](https://github.com/rwjblue))
358 |
359 | #### Committers: 1
360 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
361 |
362 | ## v4.1.0 (2019-12-10)
363 |
364 | #### :rocket: Enhancement
365 | * [#380](https://github.com/ember-cli/ember-cli-htmlbars/pull/380) Implement basic patching strategy for colocated components. ([@rwjblue](https://github.com/rwjblue))
366 |
367 | #### Committers: 1
368 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
369 |
370 | ## v4.0.9 (2019-12-04)
371 |
372 | #### :rocket: Enhancement
373 | * [#373](https://github.com/ember-cli/ember-cli-htmlbars/pull/373) Add co-location support to CoffeeScript component class files ([@locks](https://github.com/locks))
374 |
375 | #### :memo: Documentation
376 | * [#351](https://github.com/ember-cli/ember-cli-htmlbars/pull/351) Update Readme with syntax for usage with tagged templates ([@thec0keman](https://github.com/thec0keman))
377 |
378 | #### :house: Internal
379 | * [#342](https://github.com/ember-cli/ember-cli-htmlbars/pull/342) Add `ember-octane` test suite run to CI. ([@rwjblue](https://github.com/rwjblue))
380 |
381 | #### Committers: 3
382 | - John Ratcliff ([@thec0keman](https://github.com/thec0keman))
383 | - Ricardo Mendes ([@locks](https://github.com/locks))
384 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
385 |
386 | ## v4.0.8 (2019-10-19)
387 |
388 | #### :bug: Bug Fix
389 | * [#340](https://github.com/ember-cli/ember-cli-htmlbars/pull/340) Fix issues when using colocated component's with decorators. ([@rwjblue](https://github.com/rwjblue))
390 |
391 | #### :house: Internal
392 | * [#341](https://github.com/ember-cli/ember-cli-htmlbars/pull/341) Add test using native classes + decorators. ([@rwjblue](https://github.com/rwjblue))
393 | * [#338](https://github.com/ember-cli/ember-cli-htmlbars/pull/338) Add broccoli plugin + babel plugin colocation tests ([@rwjblue](https://github.com/rwjblue))
394 |
395 | #### Committers: 1
396 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
397 |
398 | ## v4.0.7 (2019-10-18)
399 |
400 | #### :bug: Bug Fix
401 | * [#336](https://github.com/ember-cli/ember-cli-htmlbars/pull/336) Support `as default` exports with template colocation ([@dfreeman](https://github.com/dfreeman))
402 |
403 | #### :house: Internal
404 | * [#335](https://github.com/ember-cli/ember-cli-htmlbars/pull/335) Add additional tests for Colocated Components ([@camerondubas](https://github.com/camerondubas))
405 |
406 | #### Committers: 2
407 | - Cameron Dubas ([@camerondubas](https://github.com/camerondubas))
408 | - Dan Freeman ([@dfreeman](https://github.com/dfreeman))
409 |
410 | ## v4.0.6 (2019-10-17)
411 |
412 | #### :rocket: Enhancement
413 | * [#334](https://github.com/ember-cli/ember-cli-htmlbars/pull/334) Add parent's name to logging output. ([@rwjblue](https://github.com/rwjblue))
414 |
415 | #### :bug: Bug Fix
416 | * [#333](https://github.com/ember-cli/ember-cli-htmlbars/pull/333) Enable colocated component classes to be TypeScript ([@rwjblue](https://github.com/rwjblue))
417 | * [#332](https://github.com/ember-cli/ember-cli-htmlbars/pull/332) Ensure "pods style" templates are compiled properly. ([@rwjblue](https://github.com/rwjblue))
418 |
419 | #### :house: Internal
420 | * [#125](https://github.com/ember-cli/ember-cli-htmlbars/pull/125) Add more tests using AST plugins (inline and standalone) ([@stefanpenner](https://github.com/stefanpenner))
421 |
422 | #### Committers: 2
423 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
424 | - Stefan Penner ([@stefanpenner](https://github.com/stefanpenner))
425 |
426 | ## v4.0.5 (2019-10-04)
427 |
428 | #### :bug: Bug Fix
429 | * [#324](https://github.com/ember-cli/ember-cli-htmlbars/pull/324) More fixes for proper babel plugin deduplication. ([@rwjblue](https://github.com/rwjblue))
430 |
431 | #### :memo: Documentation
432 | * [#323](https://github.com/ember-cli/ember-cli-htmlbars/pull/323) Ensure deprecation message shows correct heirarchy. ([@rwjblue](https://github.com/rwjblue))
433 |
434 | #### Committers: 1
435 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
436 |
437 | ## v4.0.4 (2019-10-02)
438 |
439 | #### :bug: Bug Fix
440 | * [#322](https://github.com/ember-cli/ember-cli-htmlbars/pull/322) Fix issue with deduplcation of babel plugin when parallelized ([@rwjblue](https://github.com/rwjblue))
441 |
442 | #### Committers: 1
443 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
444 |
445 | ## v4.0.3 (2019-10-01)
446 |
447 | #### :bug: Bug Fix
448 | * [#317](https://github.com/ember-cli/ember-cli-htmlbars/pull/317) Avoid conflicts with ember-cli-htmlbars-inline-precompile ([@rwjblue](https://github.com/rwjblue))
449 |
450 | #### :memo: Documentation
451 | * [#318](https://github.com/ember-cli/ember-cli-htmlbars/pull/318) Ensure all debug logging is emitted with `DEBUG=ember-cli-htmlbars:*` ([@rwjblue](https://github.com/rwjblue))
452 |
453 | #### Committers: 2
454 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
455 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
456 |
457 | ## v4.0.2 (2019-09-30)
458 |
459 | #### :bug: Bug Fix
460 | * [#309](https://github.com/ember-cli/ember-cli-htmlbars/pull/309) Ensure inline precompile and colocated templates run template AST plugins. ([@rwjblue](https://github.com/rwjblue))
461 | * [#310](https://github.com/ember-cli/ember-cli-htmlbars/pull/310) Fix issues using inline precompilation with JSON.stringify'ed options. ([@rwjblue](https://github.com/rwjblue))
462 |
463 | #### Committers: 1
464 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
465 |
466 | ## v4.0.1 (2019-09-25)
467 |
468 | #### :bug: Bug Fix
469 | * [#304](https://github.com/ember-cli/ember-cli-htmlbars/pull/304) Do nothing in ColocatedTemplateProcessor if input tree is empty. ([@rwjblue](https://github.com/rwjblue))
470 |
471 | #### Committers: 1
472 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
473 |
474 | ## v4.0.0 (2019-09-24)
475 |
476 | #### :boom: Breaking Change
477 | * [#278](https://github.com/ember-cli/ember-cli-htmlbars/pull/278) Drop Node 6 and 11 support. ([@rwjblue](https://github.com/rwjblue))
478 |
479 | #### :rocket: Enhancement
480 | * [#249](https://github.com/ember-cli/ember-cli-htmlbars/pull/249) Initial implementation of co-located templates RFC. ([@rwjblue](https://github.com/rwjblue))
481 | * [#286](https://github.com/ember-cli/ember-cli-htmlbars/pull/286) Implement inline precompilation. ([@rwjblue](https://github.com/rwjblue))
482 |
483 | #### :house: Internal
484 | * [#284](https://github.com/ember-cli/ember-cli-htmlbars/pull/284) Move code into `lib/` subdirectory. ([@rwjblue](https://github.com/rwjblue))
485 | * [#283](https://github.com/ember-cli/ember-cli-htmlbars/pull/283) Add prettier setup. ([@rwjblue](https://github.com/rwjblue))
486 | * [#281](https://github.com/ember-cli/ember-cli-htmlbars/pull/281) Add GH Actions CI setup. ([@rwjblue](https://github.com/rwjblue))
487 | * [#279](https://github.com/ember-cli/ember-cli-htmlbars/pull/279) Add tests for Ember 3.4, 3.8, and 3.12. ([@rwjblue](https://github.com/rwjblue))
488 |
489 | #### Committers: 2
490 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
491 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
492 |
493 |
--------------------------------------------------------------------------------
/node-tests/colocated-broccoli-plugin-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ColocatedTemplateCompiler = require('../lib/colocated-broccoli-plugin');
4 | const { createTempDir, createBuilder } = require('broccoli-test-helper');
5 | const { expect } = require('./assertions');
6 |
7 | describe('ColocatedTemplateCompiler', function () {
8 | this.timeout(10000);
9 |
10 | let input, output;
11 |
12 | beforeEach(async function () {
13 | input = await createTempDir();
14 | });
15 |
16 | afterEach(async function () {
17 | await input.dispose();
18 |
19 | if (output) {
20 | await output.dispose();
21 | }
22 | });
23 |
24 | it('works for template only component', async function () {
25 | input.write({
26 | 'app-name-here': {
27 | 'router.js': '// stuff here',
28 | components: {
29 | 'foo.hbs': `{{yield}}`,
30 | },
31 | templates: {
32 | 'application.hbs': `{{outlet}}`,
33 | },
34 | },
35 | });
36 |
37 | let tree = new ColocatedTemplateCompiler(input.path());
38 |
39 | output = createBuilder(tree);
40 | await output.build();
41 |
42 | expect(output.read()).toDeepEqualCode({
43 | 'app-name-here': {
44 | 'router.js': '// stuff here',
45 | components: {
46 | 'foo.js':
47 | `
48 | import { hbs } from 'ember-cli-htmlbars';
49 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
50 | import templateOnly from '@ember/component/template-only';
51 |
52 | export default templateOnly();` + '\n',
53 | },
54 | templates: {
55 | 'application.hbs': '{{outlet}}',
56 | },
57 | },
58 | });
59 |
60 | await output.build();
61 |
62 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
63 |
64 | input.write({
65 | 'app-name-here': {
66 | 'router.js': '// other stuff here',
67 | },
68 | });
69 |
70 | await output.build();
71 |
72 | expect(output.changes()).toDeepEqualCode(
73 | { 'app-name-here/router.js': 'change' },
74 | 'has only related changes',
75 | );
76 | });
77 |
78 | it('works for component with template and class', async function () {
79 | input.write({
80 | 'app-name-here': {
81 | 'router.js': '// stuff here',
82 | components: {
83 | 'foo.hbs': `{{yield}}`,
84 | 'foo.js': `
85 | import Component from '@glimmer/component';
86 |
87 | export default class FooComponent extends Component {}
88 | `,
89 | },
90 | templates: {
91 | 'application.hbs': `{{outlet}}`,
92 | },
93 | },
94 | });
95 |
96 | let tree = new ColocatedTemplateCompiler(input.path());
97 |
98 | output = createBuilder(tree);
99 | await output.build();
100 |
101 | expect(output.read()).toDeepEqualCode({
102 | 'app-name-here': {
103 | 'router.js': '// stuff here',
104 | components: {
105 | 'foo.js': `
106 | import { hbs } from 'ember-cli-htmlbars';
107 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
108 | import Component from '@glimmer/component';
109 |
110 | export default class FooComponent extends Component {}
111 | `,
112 | },
113 | templates: {
114 | 'application.hbs': '{{outlet}}',
115 | },
116 | },
117 | });
118 |
119 | await output.build();
120 |
121 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
122 |
123 | input.write({
124 | 'app-name-here': {
125 | 'router.js': '// other stuff here',
126 | },
127 | });
128 |
129 | await output.build();
130 |
131 | expect(output.changes()).toDeepEqualCode(
132 | { 'app-name-here/router.js': 'change' },
133 | 'has only related changes',
134 | );
135 | });
136 |
137 | it('works for re-exported component without a template', async function () {
138 | input.write({
139 | 'app-name-here': {
140 | 'router.js': '// stuff here',
141 | components: {
142 | 'foo.js': `export { default } from 'some-place';`,
143 | },
144 | templates: {
145 | 'application.hbs': `{{outlet}}`,
146 | },
147 | },
148 | });
149 |
150 | let tree = new ColocatedTemplateCompiler(input.path());
151 |
152 | output = createBuilder(tree);
153 | await output.build();
154 |
155 | expect(output.read()).toDeepEqualCode({
156 | 'app-name-here': {
157 | 'router.js': '// stuff here',
158 | components: {
159 | 'foo.js': `export { default } from 'some-place';`,
160 | },
161 | templates: {
162 | 'application.hbs': '{{outlet}}',
163 | },
164 | },
165 | });
166 |
167 | await output.build();
168 |
169 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
170 |
171 | input.write({
172 | 'app-name-here': {
173 | 'router.js': '// other stuff here',
174 | },
175 | });
176 |
177 | await output.build();
178 |
179 | expect(output.changes()).toDeepEqualCode(
180 | { 'app-name-here/router.js': 'change' },
181 | 'has only related changes',
182 | );
183 | });
184 |
185 | it('emits an error for re-exported components with a different template', async function () {
186 | input.write({
187 | 'app-name-here': {
188 | 'router.js': '// stuff here',
189 | components: {
190 | 'foo.hbs': `{{yield}}`,
191 | 'foo.js': `export { default } from 'some-place';`,
192 | },
193 | templates: {
194 | 'application.hbs': `{{outlet}}`,
195 | },
196 | },
197 | });
198 |
199 | let tree = new ColocatedTemplateCompiler(input.path());
200 |
201 | output = createBuilder(tree);
202 | await output.build();
203 |
204 | expect(output.read()).toDeepEqualCode({
205 | 'app-name-here': {
206 | 'router.js': '// stuff here',
207 | components: {
208 | 'foo.js': `
209 | export { default } from 'some-place';\nthrow new Error("\`app-name-here/components/foo.js\` contains an \`export { default }\` re-export, but it has a co-located template. You must explicitly extend the component to assign it a different template.");
210 | `,
211 | },
212 | templates: {
213 | 'application.hbs': '{{outlet}}',
214 | },
215 | },
216 | });
217 |
218 | await output.build();
219 |
220 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
221 |
222 | input.write({
223 | 'app-name-here': {
224 | 'router.js': '// other stuff here',
225 | },
226 | });
227 |
228 | await output.build();
229 |
230 | expect(output.changes()).toDeepEqualCode(
231 | { 'app-name-here/router.js': 'change' },
232 | 'has only related changes',
233 | );
234 | });
235 |
236 | it('works for typescript component class with template', async function () {
237 | input.write({
238 | 'app-name-here': {
239 | components: {
240 | 'foo.hbs': `{{yield}}`,
241 | 'foo.ts': `
242 | import Component from '@glimmer/component';
243 |
244 | export default class FooComponent extends Component {}
245 | `,
246 | },
247 | templates: {
248 | 'application.hbs': `{{outlet}}`,
249 | },
250 | },
251 | });
252 |
253 | let tree = new ColocatedTemplateCompiler(input.path());
254 |
255 | output = createBuilder(tree);
256 | await output.build();
257 |
258 | expect(output.read()).toDeepEqualCode({
259 | 'app-name-here': {
260 | components: {
261 | 'foo.ts': `
262 | import { hbs } from 'ember-cli-htmlbars';
263 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
264 | import Component from '@glimmer/component';
265 |
266 | export default class FooComponent extends Component {}
267 | `,
268 | },
269 | templates: {
270 | 'application.hbs': '{{outlet}}',
271 | },
272 | },
273 | });
274 |
275 | await output.build();
276 |
277 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
278 | });
279 |
280 | it('works for coffeescript component class with template', async function () {
281 | input.write({
282 | 'app-name-here': {
283 | components: {
284 | 'foo.hbs': `{{yield}}`,
285 | 'foo.coffee': `
286 | import Component from '@ember/component'
287 | export default class extends Component
288 | `,
289 | },
290 | templates: {
291 | 'application.hbs': `{{outlet}}`,
292 | },
293 | },
294 | });
295 |
296 | let tree = new ColocatedTemplateCompiler(input.path());
297 |
298 | output = createBuilder(tree);
299 | await output.build();
300 |
301 | expect(output.read()).toDeepEqualCode({
302 | 'app-name-here': {
303 | components: {
304 | 'foo.coffee': `
305 | import { hbs } from 'ember-cli-htmlbars'
306 | __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}})
307 | import Component from '@ember/component'
308 | export default class extends Component
309 | `,
310 | },
311 | templates: {
312 | 'application.hbs': '{{outlet}}',
313 | },
314 | },
315 | });
316 |
317 | await output.build();
318 |
319 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
320 | });
321 |
322 | it('works for scoped addon using template only component', async function () {
323 | input.write({
324 | '@scope-name': {
325 | 'addon-name-here': {
326 | components: {
327 | 'foo.hbs': `{{yield}}`,
328 | },
329 | templates: {
330 | 'application.hbs': `{{outlet}}`,
331 | },
332 | },
333 | },
334 | });
335 |
336 | let tree = new ColocatedTemplateCompiler(input.path());
337 |
338 | output = createBuilder(tree);
339 | await output.build();
340 |
341 | expect(output.read()).toDeepEqualCode({
342 | '@scope-name': {
343 | 'addon-name-here': {
344 | components: {
345 | 'foo.js':
346 | `
347 | import { hbs } from 'ember-cli-htmlbars';
348 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"@scope-name/addon-name-here/components/foo.hbs","parseOptions":{"srcName":"@scope-name/addon-name-here/components/foo.hbs"}});
349 | import templateOnly from '@ember/component/template-only';
350 |
351 | export default templateOnly();` + '\n',
352 | },
353 | templates: {
354 | 'application.hbs': '{{outlet}}',
355 | },
356 | },
357 | },
358 | });
359 |
360 | await output.build();
361 |
362 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
363 | });
364 |
365 | it('works for scoped addon using component with template and class', async function () {
366 | input.write({
367 | '@scope-name': {
368 | 'addon-name-here': {
369 | components: {
370 | 'foo.hbs': `{{yield}}`,
371 | 'foo.js': `
372 | import Component from '@glimmer/component';
373 |
374 | export default class FooComponent extends Component {}
375 | `,
376 | },
377 | templates: {
378 | 'application.hbs': `{{outlet}}`,
379 | },
380 | },
381 | },
382 | });
383 |
384 | let tree = new ColocatedTemplateCompiler(input.path());
385 |
386 | output = createBuilder(tree);
387 | await output.build();
388 |
389 | expect(output.read()).toDeepEqualCode({
390 | '@scope-name': {
391 | 'addon-name-here': {
392 | components: {
393 | 'foo.js': `
394 | import { hbs } from 'ember-cli-htmlbars';
395 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"@scope-name/addon-name-here/components/foo.hbs","parseOptions":{"srcName":"@scope-name/addon-name-here/components/foo.hbs"}});
396 | import Component from '@glimmer/component';
397 |
398 | export default class FooComponent extends Component {}
399 | `,
400 | },
401 | templates: {
402 | 'application.hbs': '{{outlet}}',
403 | },
404 | },
405 | },
406 | });
407 |
408 | await output.build();
409 |
410 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
411 | });
412 |
413 | it('does nothing for "classic" location components', async function () {
414 | input.write({
415 | 'app-name-here': {
416 | components: {
417 | 'foo.js': `
418 | import Component from '@glimmer/component';
419 |
420 | export default class FooComponent extends Component {}
421 | `,
422 | },
423 | templates: {
424 | 'application.hbs': `{{outlet}}`,
425 | components: {
426 | 'foo.hbs': `{{yield}}`,
427 | },
428 | },
429 | },
430 | });
431 |
432 | let tree = new ColocatedTemplateCompiler(input.path());
433 |
434 | output = createBuilder(tree);
435 | await output.build();
436 |
437 | expect(output.read()).toDeepEqualCode(input.read());
438 |
439 | await output.build();
440 |
441 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
442 | });
443 |
444 | it('does nothing for "pod" location templates', async function () {
445 | input.write({
446 | 'addon-name-here': {
447 | components: {
448 | foo: {
449 | 'template.hbs': `{{yield}}`,
450 | },
451 | },
452 | },
453 | });
454 |
455 | let tree = new ColocatedTemplateCompiler(input.path());
456 |
457 | output = createBuilder(tree);
458 | await output.build();
459 |
460 | expect(output.read()).toDeepEqualCode(input.read());
461 |
462 | await output.build();
463 |
464 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
465 | });
466 |
467 | it('it works if there are no input files', async function () {
468 | input.write({});
469 |
470 | let tree = new ColocatedTemplateCompiler(input.path());
471 |
472 | output = createBuilder(tree);
473 | await output.build();
474 |
475 | expect(output.read()).toDeepEqualCode({});
476 |
477 | await output.build();
478 |
479 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
480 | });
481 |
482 | it('it works if input is manually using setComponentTemplate - no colocated template exists', async function () {
483 | input.write({
484 | 'app-name-here': {
485 | components: {
486 | 'foo.js': `
487 | import Component from '@glimmer/component';
488 | import { setComponentTemplate } from '@ember/component';
489 | import hbs from 'ember-cli-htmlbars-inline-precompile';
490 |
491 | export default class FooComponent extends Component {}
492 | setComponentTemplate(FooComponent, hbs\`sometemplate\`);
493 | `,
494 | },
495 | templates: {
496 | 'application.hbs': `{{outlet}}`,
497 | },
498 | },
499 | });
500 |
501 | let tree = new ColocatedTemplateCompiler(input.path());
502 |
503 | output = createBuilder(tree);
504 | await output.build();
505 |
506 | expect(output.read()).toDeepEqualCode({
507 | 'app-name-here': {
508 | components: {
509 | 'foo.js': `
510 | import Component from '@glimmer/component';
511 | import { setComponentTemplate } from '@ember/component';
512 | import hbs from 'ember-cli-htmlbars-inline-precompile';
513 |
514 | export default class FooComponent extends Component {}
515 | setComponentTemplate(FooComponent, hbs\`sometemplate\`);
516 | `,
517 | },
518 | templates: {
519 | 'application.hbs': '{{outlet}}',
520 | },
521 | },
522 | });
523 |
524 | await output.build();
525 |
526 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
527 | });
528 |
529 | it('emits an error when a default export is not present in a component JS file', async function () {
530 | input.write({
531 | 'app-name-here': {
532 | components: {
533 | 'foo.hbs': `{{yield}}`,
534 | 'foo.js': `
535 | export function whatever() {}
536 | `,
537 | },
538 | },
539 | });
540 |
541 | let tree = new ColocatedTemplateCompiler(input.path());
542 |
543 | output = createBuilder(tree);
544 | await output.build();
545 |
546 | expect(output.read()).toDeepEqualCode({
547 | 'app-name-here': {
548 | components: {
549 | 'foo.js': `
550 | export function whatever() {}\nthrow new Error("\`app-name-here/components/foo.js\` does not contain a \`default export\`. Did you forget to export the component class?");
551 | `,
552 | },
553 | },
554 | });
555 |
556 | await output.build();
557 |
558 | expect(output.changes()).toDeepEqualCode({}, 'NOOP update has no changes');
559 | });
560 |
561 | it('does not break class decorator usage');
562 |
563 | describe('changes', function () {
564 | it('initial template only, add a JS file', async function () {
565 | input.write({
566 | 'app-name-here': {
567 | 'router.js': '// stuff here',
568 | components: {
569 | 'foo.hbs': `{{yield}}`,
570 | },
571 | templates: {
572 | 'application.hbs': `{{outlet}}`,
573 | },
574 | },
575 | });
576 |
577 | let tree = new ColocatedTemplateCompiler(input.path());
578 |
579 | output = createBuilder(tree);
580 | await output.build();
581 |
582 | expect(output.read()).toDeepEqualCode(
583 | {
584 | 'app-name-here': {
585 | 'router.js': '// stuff here',
586 | components: {
587 | 'foo.js':
588 | `
589 | import { hbs } from 'ember-cli-htmlbars';
590 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
591 | import templateOnly from '@ember/component/template-only';
592 |
593 | export default templateOnly();` + '\n',
594 | },
595 | templates: {
596 | 'application.hbs': '{{outlet}}',
597 | },
598 | },
599 | },
600 | 'initial content is correct',
601 | );
602 |
603 | await output.build();
604 |
605 | expect(output.changes()).toDeepEqualCode(
606 | {},
607 | 'NOOP update has no changes',
608 | );
609 |
610 | input.write({
611 | 'app-name-here': {
612 | components: {
613 | 'foo.js': `
614 | import Component from '@glimmer/component';
615 |
616 | export default class FooComponent extends Component {}
617 | `,
618 | },
619 | },
620 | });
621 |
622 | await output.build();
623 |
624 | expect(output.changes()).toDeepEqualCode(
625 | { 'app-name-here/components/foo.js': 'change' },
626 | 'has only related changes',
627 | );
628 |
629 | expect(output.read()).toDeepEqualCode(
630 | {
631 | 'app-name-here': {
632 | 'router.js': '// stuff here',
633 | components: {
634 | 'foo.js': `
635 | import { hbs } from 'ember-cli-htmlbars';
636 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
637 | import Component from '@glimmer/component';
638 |
639 | export default class FooComponent extends Component {}
640 | `,
641 | },
642 | templates: {
643 | 'application.hbs': '{{outlet}}',
644 | },
645 | },
646 | },
647 | 'content is correct after updating',
648 | );
649 | });
650 |
651 | it('initial JS only, add a template', async function () {
652 | input.write({
653 | 'app-name-here': {
654 | 'router.js': '// stuff here',
655 | components: {
656 | 'foo.js': `
657 | import Component from '@glimmer/component';
658 |
659 | export default class FooComponent extends Component {}
660 | `,
661 | },
662 | templates: {
663 | 'application.hbs': `{{outlet}}`,
664 | },
665 | },
666 | });
667 |
668 | let tree = new ColocatedTemplateCompiler(input.path());
669 |
670 | output = createBuilder(tree);
671 | await output.build();
672 |
673 | expect(output.read()).toDeepEqualCode(
674 | {
675 | 'app-name-here': {
676 | 'router.js': '// stuff here',
677 | components: {
678 | 'foo.js': `
679 | import Component from '@glimmer/component';
680 |
681 | export default class FooComponent extends Component {}
682 | `,
683 | },
684 | templates: {
685 | 'application.hbs': '{{outlet}}',
686 | },
687 | },
688 | },
689 | 'initial content is correct',
690 | );
691 |
692 | await output.build();
693 |
694 | expect(output.changes()).toDeepEqualCode(
695 | {},
696 | 'NOOP update has no changes',
697 | );
698 |
699 | input.write({
700 | 'app-name-here': {
701 | components: {
702 | 'foo.hbs': `{{yield}}`,
703 | },
704 | },
705 | });
706 |
707 | await output.build();
708 |
709 | expect(output.changes()).toDeepEqualCode(
710 | { 'app-name-here/components/foo.js': 'change' },
711 | 'has only related changes',
712 | );
713 |
714 | expect(output.read()).toDeepEqualCode(
715 | {
716 | 'app-name-here': {
717 | 'router.js': '// stuff here',
718 | components: {
719 | 'foo.js': `
720 | import { hbs } from 'ember-cli-htmlbars';
721 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
722 | import Component from '@glimmer/component';
723 |
724 | export default class FooComponent extends Component {}
725 | `,
726 | },
727 | templates: {
728 | 'application.hbs': '{{outlet}}',
729 | },
730 | },
731 | },
732 | 'content is correct after updating',
733 | );
734 | });
735 |
736 | it('initial JS only, delete JS', async function () {
737 | input.write({
738 | 'app-name-here': {
739 | 'router.js': '// stuff here',
740 | components: {
741 | 'foo.js': `
742 | import Component from '@glimmer/component';
743 |
744 | export default class FooComponent extends Component {}
745 | `,
746 | },
747 | templates: {
748 | 'application.hbs': `{{outlet}}`,
749 | },
750 | },
751 | });
752 |
753 | let tree = new ColocatedTemplateCompiler(input.path());
754 |
755 | output = createBuilder(tree);
756 | await output.build();
757 |
758 | expect(output.read()).toDeepEqualCode(
759 | {
760 | 'app-name-here': {
761 | 'router.js': '// stuff here',
762 | components: {
763 | 'foo.js': `
764 | import Component from '@glimmer/component';
765 |
766 | export default class FooComponent extends Component {}
767 | `,
768 | },
769 | templates: {
770 | 'application.hbs': '{{outlet}}',
771 | },
772 | },
773 | },
774 | 'initial content is correct',
775 | );
776 |
777 | await output.build();
778 |
779 | expect(output.changes()).toDeepEqualCode(
780 | {},
781 | 'NOOP update has no changes',
782 | );
783 |
784 | input.write({
785 | 'app-name-here': {
786 | components: {
787 | 'foo.js': null,
788 | },
789 | },
790 | });
791 |
792 | await output.build();
793 |
794 | expect(output.changes()).toDeepEqualCode(
795 | { 'app-name-here/components/foo.js': 'unlink' },
796 | 'has only related changes',
797 | );
798 |
799 | expect(output.read()).toDeepEqualCode(
800 | {
801 | 'app-name-here': {
802 | 'router.js': '// stuff here',
803 | components: {},
804 | templates: {
805 | 'application.hbs': '{{outlet}}',
806 | },
807 | },
808 | },
809 | 'content is correct after updating',
810 | );
811 | });
812 |
813 | it('initial template only, delete template', async function () {
814 | input.write({
815 | 'app-name-here': {
816 | 'router.js': '// stuff here',
817 | components: {
818 | 'foo.hbs': `{{yield}}`,
819 | },
820 | templates: {
821 | 'application.hbs': `{{outlet}}`,
822 | },
823 | },
824 | });
825 |
826 | let tree = new ColocatedTemplateCompiler(input.path());
827 |
828 | output = createBuilder(tree);
829 | await output.build();
830 |
831 | expect(output.read()).toDeepEqualCode(
832 | {
833 | 'app-name-here': {
834 | 'router.js': '// stuff here',
835 | components: {
836 | 'foo.js':
837 | `
838 | import { hbs } from 'ember-cli-htmlbars';
839 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
840 | import templateOnly from '@ember/component/template-only';
841 |
842 | export default templateOnly();` + '\n',
843 | },
844 | templates: {
845 | 'application.hbs': '{{outlet}}',
846 | },
847 | },
848 | },
849 | 'initial content is correct',
850 | );
851 |
852 | await output.build();
853 |
854 | expect(output.changes()).toDeepEqualCode(
855 | {},
856 | 'NOOP update has no changes',
857 | );
858 |
859 | input.write({
860 | 'app-name-here': {
861 | components: {
862 | 'foo.hbs': null,
863 | },
864 | },
865 | });
866 |
867 | await output.build();
868 |
869 | expect(output.changes()).toDeepEqualCode(
870 | { 'app-name-here/components/foo.js': 'unlink' },
871 | 'has only related changes',
872 | );
873 | });
874 |
875 | it('initial template, update template', async function () {
876 | input.write({
877 | 'app-name-here': {
878 | 'router.js': '// stuff here',
879 | components: {
880 | 'foo.hbs': `{{yield}}`,
881 | },
882 | templates: {
883 | 'application.hbs': `{{outlet}}`,
884 | },
885 | },
886 | });
887 |
888 | let tree = new ColocatedTemplateCompiler(input.path());
889 |
890 | output = createBuilder(tree);
891 | await output.build();
892 |
893 | expect(output.read()).toDeepEqualCode(
894 | {
895 | 'app-name-here': {
896 | 'router.js': '// stuff here',
897 | components: {
898 | 'foo.js':
899 | `
900 | import { hbs } from 'ember-cli-htmlbars';
901 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
902 | import templateOnly from '@ember/component/template-only';
903 |
904 | export default templateOnly();` + '\n',
905 | },
906 | templates: {
907 | 'application.hbs': '{{outlet}}',
908 | },
909 | },
910 | },
911 | 'initial content is correct',
912 | );
913 |
914 | await output.build();
915 |
916 | expect(output.changes()).toDeepEqualCode(
917 | {},
918 | 'NOOP update has no changes',
919 | );
920 |
921 | input.write({
922 | 'app-name-here': {
923 | components: {
924 | 'foo.hbs': 'whoops!',
925 | },
926 | },
927 | });
928 |
929 | await output.build();
930 |
931 | expect(output.changes()).toDeepEqualCode(
932 | { 'app-name-here/components/foo.js': 'change' },
933 | 'has only related changes',
934 | );
935 |
936 | expect(output.read()).toDeepEqualCode(
937 | {
938 | 'app-name-here': {
939 | 'router.js': '// stuff here',
940 | components: {
941 | 'foo.js':
942 | `
943 | import { hbs } from 'ember-cli-htmlbars';
944 | const __COLOCATED_TEMPLATE__ = hbs("whoops!", {"contents":"whoops!","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
945 | import templateOnly from '@ember/component/template-only';
946 |
947 | export default templateOnly();` + '\n',
948 | },
949 | templates: {
950 | 'application.hbs': '{{outlet}}',
951 | },
952 | },
953 | },
954 | 'updated content is correct',
955 | );
956 | });
957 |
958 | it('initial JS + template, update template', async function () {
959 | input.write({
960 | 'app-name-here': {
961 | 'router.js': '// stuff here',
962 | components: {
963 | 'foo.js': `
964 | import Component from '@glimmer/component';
965 |
966 | export default class FooComponent extends Component {}
967 | `,
968 | 'foo.hbs': '{{yield}}',
969 | },
970 | templates: {
971 | 'application.hbs': `{{outlet}}`,
972 | },
973 | },
974 | });
975 |
976 | let tree = new ColocatedTemplateCompiler(input.path());
977 |
978 | output = createBuilder(tree);
979 | await output.build();
980 |
981 | expect(output.read()).toDeepEqualCode(
982 | {
983 | 'app-name-here': {
984 | 'router.js': '// stuff here',
985 | components: {
986 | 'foo.js': `
987 | import { hbs } from 'ember-cli-htmlbars';
988 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
989 | import Component from '@glimmer/component';
990 |
991 | export default class FooComponent extends Component {}
992 | `,
993 | },
994 | templates: {
995 | 'application.hbs': '{{outlet}}',
996 | },
997 | },
998 | },
999 | 'initial content is correct',
1000 | );
1001 |
1002 | await output.build();
1003 |
1004 | expect(output.changes()).toDeepEqualCode(
1005 | {},
1006 | 'NOOP update has no changes',
1007 | );
1008 |
1009 | input.write({
1010 | 'app-name-here': {
1011 | components: {
1012 | 'foo.hbs': `whoops!`,
1013 | },
1014 | },
1015 | });
1016 |
1017 | await output.build();
1018 |
1019 | expect(output.changes()).toDeepEqualCode(
1020 | { 'app-name-here/components/foo.js': 'change' },
1021 | 'has only related changes',
1022 | );
1023 |
1024 | expect(output.read()).toDeepEqualCode(
1025 | {
1026 | 'app-name-here': {
1027 | 'router.js': '// stuff here',
1028 | components: {
1029 | 'foo.js': `
1030 | import { hbs } from 'ember-cli-htmlbars';
1031 | const __COLOCATED_TEMPLATE__ = hbs("whoops!", {"contents":"whoops!","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
1032 | import Component from '@glimmer/component';
1033 |
1034 | export default class FooComponent extends Component {}
1035 | `,
1036 | },
1037 | templates: {
1038 | 'application.hbs': '{{outlet}}',
1039 | },
1040 | },
1041 | },
1042 | 'content is correct after updating',
1043 | );
1044 | });
1045 |
1046 | it('initial JS + template, update JS', async function () {
1047 | input.write({
1048 | 'app-name-here': {
1049 | 'router.js': '// stuff here',
1050 | components: {
1051 | 'foo.js': `
1052 | import Component from '@glimmer/component';
1053 |
1054 | export default class FooComponent extends Component {}
1055 | `,
1056 | 'foo.hbs': '{{yield}}',
1057 | },
1058 | templates: {
1059 | 'application.hbs': `{{outlet}}`,
1060 | },
1061 | },
1062 | });
1063 |
1064 | let tree = new ColocatedTemplateCompiler(input.path());
1065 |
1066 | output = createBuilder(tree);
1067 | await output.build();
1068 |
1069 | expect(output.read()).toDeepEqualCode(
1070 | {
1071 | 'app-name-here': {
1072 | 'router.js': '// stuff here',
1073 | components: {
1074 | 'foo.js': `
1075 | import { hbs } from 'ember-cli-htmlbars';
1076 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
1077 | import Component from '@glimmer/component';
1078 |
1079 | export default class FooComponent extends Component {}
1080 | `,
1081 | },
1082 | templates: {
1083 | 'application.hbs': '{{outlet}}',
1084 | },
1085 | },
1086 | },
1087 | 'initial content is correct',
1088 | );
1089 |
1090 | await output.build();
1091 |
1092 | expect(output.changes()).toDeepEqualCode(
1093 | {},
1094 | 'NOOP update has no changes',
1095 | );
1096 |
1097 | input.write({
1098 | 'app-name-here': {
1099 | components: {
1100 | 'foo.js': `
1101 | import Component from '@glimmer/component';
1102 |
1103 | export default class FooBarComponent extends Component {}
1104 | `,
1105 | },
1106 | },
1107 | });
1108 |
1109 | await output.build();
1110 |
1111 | expect(output.read()).toDeepEqualCode(
1112 | {
1113 | 'app-name-here': {
1114 | 'router.js': '// stuff here',
1115 | components: {
1116 | 'foo.js': `
1117 | import { hbs } from 'ember-cli-htmlbars';
1118 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
1119 | import Component from '@glimmer/component';
1120 |
1121 | export default class FooBarComponent extends Component {}
1122 | `,
1123 | },
1124 | templates: {
1125 | 'application.hbs': '{{outlet}}',
1126 | },
1127 | },
1128 | },
1129 | 'content is correct after updating',
1130 | );
1131 |
1132 | expect(output.changes()).toDeepEqualCode(
1133 | { 'app-name-here/components/foo.js': 'change' },
1134 | 'has only related changes',
1135 | );
1136 | });
1137 |
1138 | it('initial JS + template, delete JS file', async function () {
1139 | input.write({
1140 | 'app-name-here': {
1141 | 'router.js': '// stuff here',
1142 | components: {
1143 | 'foo.js': `
1144 | import Component from '@glimmer/component';
1145 |
1146 | export default class FooComponent extends Component {}
1147 | `,
1148 | 'foo.hbs': '{{yield}}',
1149 | },
1150 | templates: {
1151 | 'application.hbs': `{{outlet}}`,
1152 | },
1153 | },
1154 | });
1155 |
1156 | let tree = new ColocatedTemplateCompiler(input.path());
1157 |
1158 | output = createBuilder(tree);
1159 | await output.build();
1160 |
1161 | expect(output.read()).toDeepEqualCode(
1162 | {
1163 | 'app-name-here': {
1164 | 'router.js': '// stuff here',
1165 | components: {
1166 | 'foo.js': `
1167 | import { hbs } from 'ember-cli-htmlbars';
1168 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
1169 | import Component from '@glimmer/component';
1170 |
1171 | export default class FooComponent extends Component {}
1172 | `,
1173 | },
1174 | templates: {
1175 | 'application.hbs': '{{outlet}}',
1176 | },
1177 | },
1178 | },
1179 | 'initial content is correct',
1180 | );
1181 |
1182 | await output.build();
1183 |
1184 | expect(output.changes()).toDeepEqualCode(
1185 | {},
1186 | 'NOOP update has no changes',
1187 | );
1188 |
1189 | input.write({
1190 | 'app-name-here': {
1191 | components: {
1192 | 'foo.js': null,
1193 | },
1194 | },
1195 | });
1196 |
1197 | await output.build();
1198 |
1199 | expect(output.changes()).toDeepEqualCode(
1200 | { 'app-name-here/components/foo.js': 'change' },
1201 | 'has only related changes',
1202 | );
1203 |
1204 | expect(output.read()).toDeepEqualCode(
1205 | {
1206 | 'app-name-here': {
1207 | 'router.js': '// stuff here',
1208 | components: {
1209 | 'foo.js':
1210 | `
1211 | import { hbs } from 'ember-cli-htmlbars';
1212 | const __COLOCATED_TEMPLATE__ = hbs("{{yield}}", {"contents":"{{yield}}","moduleName":"app-name-here/components/foo.hbs","parseOptions":{"srcName":"app-name-here/components/foo.hbs"}});
1213 | import templateOnly from '@ember/component/template-only';
1214 |
1215 | export default templateOnly();` + '\n',
1216 | },
1217 | templates: {
1218 | 'application.hbs': '{{outlet}}',
1219 | },
1220 | },
1221 | },
1222 | 'content is correct after updating',
1223 | );
1224 | });
1225 | });
1226 | });
1227 |
--------------------------------------------------------------------------------