├── .editorconfig
├── .ember-cli
├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ ├── ci.yml
│ └── publish.yml
├── .gitignore
├── .jshintignore
├── .jshintrc
├── .npmignore
├── .prettierrc.js
├── .stylelintignore
├── .stylelintrc.js
├── .template-lintrc.js
├── .watchmanconfig
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE.md
├── README.md
├── RELEASE.md
├── config
└── targets.js
├── ember-cli-build.js
├── lib
├── colocated-babel-plugin.js
├── colocated-broccoli-plugin.js
├── ember-addon-main.js
├── index.d.ts
├── index.js
├── plugin-launcher.js
├── template-compiler-plugin.js
└── utils.js
├── node-tests
├── assertions.js
├── colocated-babel-plugin-test.js
├── colocated-broccoli-plugin-test.js
├── colocated-test.js
├── fixtures
│ ├── compiler.js
│ ├── non-standard-extension.handlebars
│ ├── template-with-bom.hbs
│ ├── template.hbs
│ ├── template.tagname
│ └── web-component-template.hbs
├── template_compiler_test.js
└── utils_test.js
├── package.json
├── public
└── robots.txt
├── testem.js
├── tests
├── acceptance
│ └── styleguide-test.js
├── colocation
│ └── app
│ │ └── components
│ │ ├── bar.hbs
│ │ ├── bar.js
│ │ ├── baz
│ │ └── index.hbs
│ │ ├── foo.hbs
│ │ ├── foo
│ │ ├── bar.hbs
│ │ └── baz
│ │ │ └── index.hbs
│ │ ├── its-native.hbs
│ │ └── its-native.js
├── dummy
│ ├── app
│ │ ├── app.js
│ │ ├── components
│ │ │ └── pods-comp
│ │ │ │ └── template.hbs
│ │ ├── index.html
│ │ ├── resolver.js
│ │ ├── router.js
│ │ ├── styles
│ │ │ └── app.css
│ │ └── templates
│ │ │ ├── application.hbs
│ │ │ ├── components
│ │ │ ├── x-module-name-inlined-component.hbs
│ │ │ └── x-module-name-reversed-component.hbs
│ │ │ └── styleguide.hbs
│ ├── config
│ │ ├── ember-cli-update.json
│ │ ├── ember-try.js
│ │ ├── environment.js
│ │ ├── optional-features.json
│ │ └── targets.js
│ ├── lib
│ │ └── module-name-inliner
│ │ │ ├── index.js
│ │ │ └── package.json
│ └── public
│ │ └── robots.txt
├── helpers
│ └── index.js
├── index.html
├── integration
│ ├── .gitkeep
│ └── components
│ │ ├── ast-plugins-test.js
│ │ ├── colocation-test.js
│ │ └── test-inline-precompile-test.js
├── test-helper.js
└── unit
│ └── .gitkeep
├── tsconfig.declarations.json
└── yarn.lock
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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: 18.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: [18.x, 20.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: 18.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: 18.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | .git
2 | bower_components
3 | node_modules
4 | tmp
5 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | overrides: [
3 | {
4 | files: '*.{js,ts}',
5 | options: {
6 | singleQuote: true,
7 | },
8 | },
9 | ],
10 | };
11 |
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | # unconventional files
2 | /blueprints/*/files/
3 |
4 | # compiled output
5 | /dist/
6 |
7 | # addons
8 | /.node_modules.ember-try/
9 |
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'],
5 | };
6 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## v6.3.0 (2023-08-08)
9 |
10 | #### :rocket: Enhancement
11 | * [#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))
12 |
13 | #### :bug: Bug Fix
14 | * [#764](https://github.com/ember-cli/ember-cli-htmlbars/pull/764) Export non-user-constructible `TemplateFactory` type ([@chriskrycho](https://github.com/chriskrycho))
15 |
16 | #### Committers: 2
17 | - Chris Krycho ([@chriskrycho](https://github.com/chriskrycho))
18 | - Robin Borst ([@robinborst95](https://github.com/robinborst95))
19 |
20 |
21 | ## v6.2.0 (2023-01-17)
22 |
23 | #### :rocket: Enhancement
24 | * [#762](https://github.com/ember-cli/ember-cli-htmlbars/pull/762) Upgrade to `babel-plugin-ember-template-compilation` v2 ([@dfreeman](https://github.com/dfreeman))
25 |
26 | #### Committers: 1
27 | - Dan Freeman ([@dfreeman](https://github.com/dfreeman))
28 |
29 |
30 | ## v6.1.1 (2022-09-08)
31 |
32 | #### :bug: Bug Fix
33 | * [#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))
34 |
35 | #### Committers: 1
36 | - Chris Manson ([@mansona](https://github.com/mansona))
37 |
38 | ## v6.1.0 (2022-07-04)
39 |
40 | #### :rocket: Enhancement
41 | * [#749](https://github.com/ember-cli/ember-cli-htmlbars/pull/749) Drive all template compilation from babel ([@ef4](https://github.com/ef4))
42 |
43 | #### :bug: Bug Fix
44 | * [#747](https://github.com/ember-cli/ember-cli-htmlbars/pull/747) Avoid registering `babel-plugin-ember-template-compilation` repeatedly ([@dfreeman](https://github.com/dfreeman))
45 | * [#741](https://github.com/ember-cli/ember-cli-htmlbars/pull/741) Fix incorrect ember-source version check ([@simonihmig](https://github.com/simonihmig))
46 |
47 | #### :memo: Documentation
48 | * [#743](https://github.com/ember-cli/ember-cli-htmlbars/pull/743) Fix lin to ASTPluginBuilder type ([@mehulkar](https://github.com/mehulkar))
49 |
50 | #### Committers: 3
51 | - Edward Faulkner ([@ef4](https://github.com/ef4))
52 | - Dan Freeman ([@dfreeman](https://github.com/dfreeman))
53 | - Mehul Kar ([@mehulkar](https://github.com/mehulkar))
54 | - Simon Ihmig ([@simonihmig](https://github.com/simonihmig))
55 |
56 | ## v6.0.1 (2021-12-05)
57 |
58 | #### :bug: Bug Fix
59 | * [#739](https://github.com/ember-cli/ember-cli-htmlbars/pull/739) Allow console messages (v6.x, master) ([@mixonic](https://github.com/mixonic))
60 |
61 | #### Committers: 1
62 | - Matthew Beale ([@mixonic](https://github.com/mixonic))
63 |
64 |
65 | ## v6.0.0 (2021-10-14)
66 |
67 | #### :boom: Breaking Change
68 | * [#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))
69 |
70 | #### :rocket: Enhancement
71 | * [#733](https://github.com/ember-cli/ember-cli-htmlbars/pull/733) Avoid repeated encoding in getTemplateCompiler ([@rwjblue](https://github.com/rwjblue))
72 | * [#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))
73 |
74 | #### :bug: Bug Fix
75 | * [#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))
76 |
77 | #### :house: Internal
78 | * [#736](https://github.com/ember-cli/ember-cli-htmlbars/pull/736) Updating pr 723 ([@ef4](https://github.com/ef4))
79 |
80 | #### Committers: 3
81 | - Edward Faulkner ([@ef4](https://github.com/ef4))
82 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
83 | - Stanley Stuart ([@fivetanley](https://github.com/fivetanley))
84 |
85 |
86 | ## v5.7.1 (2021-03-18)
87 |
88 | #### :bug: Bug Fix
89 | * [#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))
90 |
91 | #### Committers: 1
92 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
93 |
94 |
95 | ## v5.7.0 (2021-03-18)
96 |
97 | #### :rocket: Enhancement
98 | * [#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))
99 |
100 | #### :house: Internal
101 | * [#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))
102 |
103 | #### Committers: 2
104 | - Chris Garrett ([@pzuraq](https://github.com/pzuraq))
105 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
106 |
107 |
108 | ## v5.6.5 (2021-03-12)
109 |
110 | #### :bug: Bug Fix
111 | * [#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))
112 |
113 | #### Committers: 2
114 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
115 |
116 |
117 | ## v5.6.4 (2021-03-07)
118 |
119 | #### :bug: Bug Fix
120 | * [#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))
121 | * [#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))
122 |
123 | #### Committers: 2
124 | - Dan Freeman ([@dfreeman](https://github.com/dfreeman))
125 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
126 |
127 |
128 | ## v5.6.3 (2021-03-04)
129 |
130 | #### :bug: Bug Fix
131 | * [#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))
132 |
133 | #### Committers: 1
134 | - Alex LaFroscia ([@alexlafroscia](https://github.com/alexlafroscia))
135 |
136 |
137 | ## v5.6.2 (2021-02-27)
138 |
139 | #### :bug: Bug Fix
140 | * [#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))
141 |
142 | #### Committers: 1
143 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
144 |
145 |
146 | ## v5.6.1 (2021-02-26)
147 |
148 | #### :bug: Bug Fix
149 | * [#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))
150 |
151 | #### Committers: 1
152 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
153 |
154 |
155 | ## v5.6.0 (2021-02-26)
156 |
157 | #### :rocket: Enhancement
158 | * [#661](https://github.com/ember-cli/ember-cli-htmlbars/pull/661) Remove usage of registerPlugin / unregisterPlugin ([@rwjblue](https://github.com/rwjblue))
159 |
160 | #### :bug: Bug Fix
161 | * [#662](https://github.com/ember-cli/ember-cli-htmlbars/pull/662) Avoid building the template compiler cache key repeatedly ([@rwjblue](https://github.com/rwjblue))
162 |
163 | #### Committers: 1
164 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
165 |
166 |
167 | ## v5.5.0 (2021-02-26)
168 |
169 | #### :rocket: Enhancement
170 | * [#660](https://github.com/ember-cli/ember-cli-htmlbars/pull/660) Replace `purgeModule` cache busting with `vm` based sandboxing ([@rwjblue](https://github.com/rwjblue))
171 |
172 | #### Committers: 1
173 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
174 |
175 |
176 | ## v5.4.0 (2021-02-24)
177 |
178 | #### :house: Internal
179 | * [#659](https://github.com/ember-cli/ember-cli-htmlbars/pull/659) Enable experimentation via `ember-template-imports` addon ([@pzuraq](https://github.com/pzuraq))
180 |
181 | #### Committers: 1
182 | - Chris Garrett ([@pzuraq](https://github.com/pzuraq))
183 |
184 |
185 | ## v5.3.2 (2021-02-10)
186 |
187 | #### :rocket: Enhancement
188 | * [#657](https://github.com/ember-cli/ember-cli-htmlbars/pull/657) Make cacheKey lazy ([@krisselden](https://github.com/krisselden))
189 |
190 | #### Committers: 2
191 | - Kris Selden ([@krisselden](https://github.com/krisselden))
192 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
193 |
194 |
195 | ## v5.3.1 (2020-08-11)
196 |
197 | #### :bug: Bug Fix
198 | * [#599](https://github.com/ember-cli/ember-cli-htmlbars/pull/599) Move `ember-template-lint` to `devDependencies` (from `dependencies`) ([@jamescdavis](https://github.com/jamescdavis))
199 |
200 | #### Committers: 1
201 | - James C. Davis ([@jamescdavis](https://github.com/jamescdavis))
202 |
203 | ## v5.3.0 (2020-08-10)
204 |
205 | #### :rocket: Enhancement
206 | * [#597](https://github.com/ember-cli/ember-cli-htmlbars/pull/597) Pass `isProduction` to Ember template compiler. ([@rwjblue](https://github.com/rwjblue))
207 |
208 | #### :memo: Documentation
209 | * [#585](https://github.com/ember-cli/ember-cli-htmlbars/pull/585) Refactor README ([@rwjblue](https://github.com/rwjblue))
210 |
211 | #### :house: Internal
212 | * [#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))
213 |
214 | #### Committers: 2
215 | - Arthi ([@arthirm](https://github.com/arthirm))
216 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
217 |
218 |
219 | ## v5.2.0 (2020-06-25)
220 |
221 | #### :rocket: Enhancement
222 | * [#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))
223 |
224 | #### Committers: 1
225 | - Chris Eppstein ([@chriseppstein](https://github.com/chriseppstein))
226 |
227 |
228 | ## v5.1.2 (2020-05-08)
229 |
230 | #### :bug: Bug Fix
231 | * [#553](https://github.com/ember-cli/ember-cli-htmlbars/pull/553) Ensure custom templateCompilerPath is an absolute path. ([@rwjblue](https://github.com/rwjblue))
232 |
233 | #### Committers: 1
234 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
235 |
236 |
237 | ## v5.1.1 (2020-05-07)
238 |
239 | #### :bug: Bug Fix
240 | * [#551](https://github.com/ember-cli/ember-cli-htmlbars/pull/551) Ensure `EmberENV` is available to inline template compilation ([@rwjblue](https://github.com/rwjblue))
241 | * [#550](https://github.com/ember-cli/ember-cli-htmlbars/pull/550) Fix specifying custom template compiler path. ([@rwjblue](https://github.com/rwjblue))
242 |
243 | #### :house: Internal
244 | * [#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))
245 | * [#544](https://github.com/ember-cli/ember-cli-htmlbars/pull/544) Add Node 14 to CI ([@rwjblue](https://github.com/rwjblue))
246 |
247 | #### Committers: 1
248 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
249 |
250 |
251 | ## v5.1.0 (2020-05-06)
252 |
253 | #### :rocket: Enhancement
254 | * [#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))
255 |
256 | #### Committers: 1
257 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
258 |
259 |
260 | ## v5.0.0 (2020-05-04)
261 |
262 | #### :boom: Breaking Change
263 | * [#496](https://github.com/ember-cli/ember-cli-htmlbars/pull/496) Drop support for Ember < 3.8. ([@rwjblue](https://github.com/rwjblue))
264 | * [#493](https://github.com/ember-cli/ember-cli-htmlbars/pull/493) Drop Node 8 support. ([@rwjblue](https://github.com/rwjblue))
265 | * [#492](https://github.com/ember-cli/ember-cli-htmlbars/pull/492) Remove Bower support. ([@rwjblue](https://github.com/rwjblue))
266 |
267 | #### :rocket: Enhancement
268 | * [#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))
269 | * [#512](https://github.com/ember-cli/ember-cli-htmlbars/pull/512) Update Broccoli dependencies to latest. ([@rwjblue](https://github.com/rwjblue))
270 |
271 | #### :house: Internal
272 | * [#514](https://github.com/ember-cli/ember-cli-htmlbars/pull/514) Update fixturify and qunit-dom to latest. ([@rwjblue](https://github.com/rwjblue))
273 | * [#513](https://github.com/ember-cli/ember-cli-htmlbars/pull/513) Update semver to 7.1.2. ([@rwjblue](https://github.com/rwjblue))
274 | * [#508](https://github.com/ember-cli/ember-cli-htmlbars/pull/508) Update to prettier@2. ([@rwjblue](https://github.com/rwjblue))
275 | * [#507](https://github.com/ember-cli/ember-cli-htmlbars/pull/507) Update Babel related devDependencies. ([@rwjblue](https://github.com/rwjblue))
276 |
277 | #### Committers: 2
278 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
279 | - Thomas Wang ([@xg-wang](https://github.com/xg-wang))
280 |
281 |
282 | ## v4.3.1 (2020-04-09)
283 |
284 | #### :bug: Bug Fix
285 | * [#494](https://github.com/ember-cli/ember-cli-htmlbars/pull/494) Ensure types file gets published. ([@rwjblue](https://github.com/rwjblue))
286 |
287 | #### Committers: 1
288 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
289 |
290 | ## v4.3.0 (2020-04-08)
291 |
292 | #### :memo: Documentation
293 | * [#481](https://github.com/ember-cli/ember-cli-htmlbars/pull/481) Add type definition for test helper import ([@chriskrycho](https://github.com/chriskrycho))
294 |
295 | #### Committers: 1
296 | - Chris Krycho ([@chriskrycho](https://github.com/chriskrycho))
297 |
298 | ## v4.2.3 (2020-02-24)
299 |
300 | #### :house: Internal
301 | * [#464](https://github.com/ember-cli/ember-cli-htmlbars/pull/464) Remove usage of legacy `checker.forEmber` API. ([@rwjblue](https://github.com/rwjblue))
302 | * [#463](https://github.com/ember-cli/ember-cli-htmlbars/pull/463) fix: Standardize the option name for dependency invalidation. ([@chriseppstein](https://github.com/chriseppstein))
303 |
304 | #### Committers: 2
305 | - Chris Eppstein ([@chriseppstein](https://github.com/chriseppstein))
306 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
307 |
308 | ## v4.2.2 (2020-01-15)
309 |
310 | #### :bug: Bug Fix
311 | * [#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))
312 |
313 | #### :memo: Documentation
314 | * [#425](https://github.com/ember-cli/ember-cli-htmlbars/pull/425) Changelog: Fix wrong version ([@Turbo87](https://github.com/Turbo87))
315 |
316 | #### Committers: 3
317 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
318 | - Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))
319 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
320 |
321 | ## v4.2.1 (2020-01-09)
322 |
323 | #### :bug: Bug Fix
324 | * [#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))
325 | * [#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))
326 |
327 | #### Committers: 2
328 | - Matthew Beale ([@mixonic](https://github.com/mixonic))
329 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
330 |
331 | ## v4.2.0 (2019-12-11)
332 |
333 | #### :rocket: Enhancement
334 | * [#384](https://github.com/ember-cli/ember-cli-htmlbars/pull/384) Remove `setEdition` requirement for colocation. ([@rwjblue](https://github.com/rwjblue))
335 |
336 | #### Committers: 1
337 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
338 |
339 | ## v4.1.1 (2019-12-11)
340 |
341 | #### :bug: Bug Fix
342 | * [#390](https://github.com/ember-cli/ember-cli-htmlbars/pull/390) Ensure reexported components do not throw an error. ([@rwjblue](https://github.com/rwjblue))
343 |
344 | #### Committers: 1
345 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
346 |
347 | ## v4.1.0 (2019-12-10)
348 |
349 | #### :rocket: Enhancement
350 | * [#380](https://github.com/ember-cli/ember-cli-htmlbars/pull/380) Implement basic patching strategy for colocated components. ([@rwjblue](https://github.com/rwjblue))
351 |
352 | #### Committers: 1
353 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
354 |
355 | ## v4.0.9 (2019-12-04)
356 |
357 | #### :rocket: Enhancement
358 | * [#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))
359 |
360 | #### :memo: Documentation
361 | * [#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))
362 |
363 | #### :house: Internal
364 | * [#342](https://github.com/ember-cli/ember-cli-htmlbars/pull/342) Add `ember-octane` test suite run to CI. ([@rwjblue](https://github.com/rwjblue))
365 |
366 | #### Committers: 3
367 | - John Ratcliff ([@thec0keman](https://github.com/thec0keman))
368 | - Ricardo Mendes ([@locks](https://github.com/locks))
369 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
370 |
371 | ## v4.0.8 (2019-10-19)
372 |
373 | #### :bug: Bug Fix
374 | * [#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))
375 |
376 | #### :house: Internal
377 | * [#341](https://github.com/ember-cli/ember-cli-htmlbars/pull/341) Add test using native classes + decorators. ([@rwjblue](https://github.com/rwjblue))
378 | * [#338](https://github.com/ember-cli/ember-cli-htmlbars/pull/338) Add broccoli plugin + babel plugin colocation tests ([@rwjblue](https://github.com/rwjblue))
379 |
380 | #### Committers: 1
381 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
382 |
383 | ## v4.0.7 (2019-10-18)
384 |
385 | #### :bug: Bug Fix
386 | * [#336](https://github.com/ember-cli/ember-cli-htmlbars/pull/336) Support `as default` exports with template colocation ([@dfreeman](https://github.com/dfreeman))
387 |
388 | #### :house: Internal
389 | * [#335](https://github.com/ember-cli/ember-cli-htmlbars/pull/335) Add additional tests for Colocated Components ([@camerondubas](https://github.com/camerondubas))
390 |
391 | #### Committers: 2
392 | - Cameron Dubas ([@camerondubas](https://github.com/camerondubas))
393 | - Dan Freeman ([@dfreeman](https://github.com/dfreeman))
394 |
395 | ## v4.0.6 (2019-10-17)
396 |
397 | #### :rocket: Enhancement
398 | * [#334](https://github.com/ember-cli/ember-cli-htmlbars/pull/334) Add parent's name to logging output. ([@rwjblue](https://github.com/rwjblue))
399 |
400 | #### :bug: Bug Fix
401 | * [#333](https://github.com/ember-cli/ember-cli-htmlbars/pull/333) Enable colocated component classes to be TypeScript ([@rwjblue](https://github.com/rwjblue))
402 | * [#332](https://github.com/ember-cli/ember-cli-htmlbars/pull/332) Ensure "pods style" templates are compiled properly. ([@rwjblue](https://github.com/rwjblue))
403 |
404 | #### :house: Internal
405 | * [#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))
406 |
407 | #### Committers: 2
408 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
409 | - Stefan Penner ([@stefanpenner](https://github.com/stefanpenner))
410 |
411 | ## v4.0.5 (2019-10-04)
412 |
413 | #### :bug: Bug Fix
414 | * [#324](https://github.com/ember-cli/ember-cli-htmlbars/pull/324) More fixes for proper babel plugin deduplication. ([@rwjblue](https://github.com/rwjblue))
415 |
416 | #### :memo: Documentation
417 | * [#323](https://github.com/ember-cli/ember-cli-htmlbars/pull/323) Ensure deprecation message shows correct heirarchy. ([@rwjblue](https://github.com/rwjblue))
418 |
419 | #### Committers: 1
420 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
421 |
422 | ## v4.0.4 (2019-10-02)
423 |
424 | #### :bug: Bug Fix
425 | * [#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))
426 |
427 | #### Committers: 1
428 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
429 |
430 | ## v4.0.3 (2019-10-01)
431 |
432 | #### :bug: Bug Fix
433 | * [#317](https://github.com/ember-cli/ember-cli-htmlbars/pull/317) Avoid conflicts with ember-cli-htmlbars-inline-precompile ([@rwjblue](https://github.com/rwjblue))
434 |
435 | #### :memo: Documentation
436 | * [#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))
437 |
438 | #### Committers: 2
439 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
440 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
441 |
442 | ## v4.0.2 (2019-09-30)
443 |
444 | #### :bug: Bug Fix
445 | * [#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))
446 | * [#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))
447 |
448 | #### Committers: 1
449 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
450 |
451 | ## v4.0.1 (2019-09-25)
452 |
453 | #### :bug: Bug Fix
454 | * [#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))
455 |
456 | #### Committers: 1
457 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
458 |
459 | ## v4.0.0 (2019-09-24)
460 |
461 | #### :boom: Breaking Change
462 | * [#278](https://github.com/ember-cli/ember-cli-htmlbars/pull/278) Drop Node 6 and 11 support. ([@rwjblue](https://github.com/rwjblue))
463 |
464 | #### :rocket: Enhancement
465 | * [#249](https://github.com/ember-cli/ember-cli-htmlbars/pull/249) Initial implementation of co-located templates RFC. ([@rwjblue](https://github.com/rwjblue))
466 | * [#286](https://github.com/ember-cli/ember-cli-htmlbars/pull/286) Implement inline precompilation. ([@rwjblue](https://github.com/rwjblue))
467 |
468 | #### :house: Internal
469 | * [#284](https://github.com/ember-cli/ember-cli-htmlbars/pull/284) Move code into `lib/` subdirectory. ([@rwjblue](https://github.com/rwjblue))
470 | * [#283](https://github.com/ember-cli/ember-cli-htmlbars/pull/283) Add prettier setup. ([@rwjblue](https://github.com/rwjblue))
471 | * [#281](https://github.com/ember-cli/ember-cli-htmlbars/pull/281) Add GH Actions CI setup. ([@rwjblue](https://github.com/rwjblue))
472 | * [#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))
473 |
474 | #### Committers: 2
475 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue))
476 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
477 |
478 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 v18 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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`
{{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 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | TemplateCompilerPlugin: require('./template-compiler-plugin'), 3 | }; 4 | -------------------------------------------------------------------------------- /lib/plugin-launcher.js: -------------------------------------------------------------------------------- 1 | module.exports = function (params) { 2 | return require(params.requireFile)[params.buildUsing](params.params).plugin; 3 | }; 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /node-tests/fixtures/compiler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function () { 4 | return 'I AM MODULE OF COMPILER'; 5 | }; 6 | -------------------------------------------------------------------------------- /node-tests/fixtures/non-standard-extension.handlebars: -------------------------------------------------------------------------------- 1 |