├── .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 | Build Status 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``); 50 | * assert.ok(true); 51 | * }); 52 | * }); 53 | * ``` 54 | * 55 | * ## With string and options 56 | * 57 | * ```ts 58 | * import Component from '@glimmer/component'; 59 | * import { setComponentTemplate } from '@ember/component'; 60 | * import { hbs } from 'ember-cli-htmlbars'; 61 | * 62 | * class Hello extends Component { 63 | * greeting = 'hello world'; 64 | * } 65 | * 66 | * setComponentTemplate( 67 | * hbs('

{{this.greeting}}

', { moduleName: 'hello.hbs' }), 68 | * MyComponent 69 | * ); 70 | * ``` 71 | */ 72 | export function hbs(template: string, options?: PrecompileOptions): TemplateFactory; 73 | export function hbs(tagged: TemplateStringsArray): TemplateFactory; 74 | -------------------------------------------------------------------------------- /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 |
2 | {{name}} 3 |
-------------------------------------------------------------------------------- /node-tests/fixtures/template-with-bom.hbs: -------------------------------------------------------------------------------- 1 | 
2 | {{#foo-bar}} 3 | {{name}} 4 | {{/foo-bar}} 5 |
-------------------------------------------------------------------------------- /node-tests/fixtures/template.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#foo-bar}} 3 | {{name}} 4 | {{/foo-bar}} 5 |
-------------------------------------------------------------------------------- /node-tests/fixtures/template.tagname: -------------------------------------------------------------------------------- 1 | MyCustomElement -------------------------------------------------------------------------------- /node-tests/fixtures/web-component-template.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /node-tests/template_compiler_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const TemplateCompiler = require('../lib/template-compiler-plugin'); 5 | const { createTempDir, createBuilder } = require('broccoli-test-helper'); 6 | const fixturify = require('fixturify'); 7 | const jsStringEscape = require('js-string-escape'); 8 | 9 | describe('TemplateCompiler', function () { 10 | this.timeout(10000); 11 | 12 | let input, output, builder; 13 | 14 | beforeEach(async function () { 15 | input = await createTempDir(); 16 | input.write(fixturify.readSync(`${__dirname}/fixtures`)); 17 | }); 18 | 19 | afterEach(async function () { 20 | if (builder) { 21 | builder.cleanup(); 22 | } 23 | await input.dispose(); 24 | if (output) { 25 | await output.dispose(); 26 | } 27 | }); 28 | 29 | it('converts hbs into JS', async function () { 30 | let tree = new TemplateCompiler(input.path()); 31 | 32 | output = createBuilder(tree); 33 | await output.build(); 34 | 35 | let source = input.readText('template.hbs'); 36 | let expected = [ 37 | `import { hbs } from 'ember-cli-htmlbars';`, 38 | `export default hbs('${jsStringEscape( 39 | source, 40 | )}', { moduleName: 'template.hbs' });`, 41 | '', 42 | ].join('\n'); 43 | assert.strictEqual(output.readText('template.js'), expected); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /node-tests/utils_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const utils = require('../lib/utils'); 4 | const assert = require('assert'); 5 | 6 | describe('utils', function () { 7 | describe('isColocatedBabelPluginRegistered', function () { 8 | it('is false when no plugins exist', function () { 9 | let plugins = []; 10 | 11 | assert.strictEqual( 12 | utils.isColocatedBabelPluginRegistered(plugins), 13 | false, 14 | ); 15 | }); 16 | 17 | it('detects when the plugin exists', function () { 18 | let plugins = [require.resolve('../lib/colocated-babel-plugin')]; 19 | 20 | assert.strictEqual(utils.isColocatedBabelPluginRegistered(plugins), true); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-htmlbars", 3 | "version": "6.3.0", 4 | "description": "A library for adding htmlbars to ember CLI", 5 | "keywords": [ 6 | "ember-addon", 7 | "ember-cli" 8 | ], 9 | "homepage": "https://github.com/ember-cli/ember-cli-htmlbars", 10 | "bugs": { 11 | "url": "https://github.com/ember-cli/ember-cli-htmlbars/issues" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git@github.com:ember-cli/ember-cli-htmlbars.git" 16 | }, 17 | "license": "MIT", 18 | "author": "Jonathan Jackson & Chase McCarthy", 19 | "main": "lib/index.js", 20 | "types": "lib/index.d.ts", 21 | "files": [ 22 | "lib/" 23 | ], 24 | "scripts": { 25 | "build": "ember build", 26 | "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"", 27 | "lint:css": "stylelint \"**/*.css\"", 28 | "lint:css:fix": "concurrently \"npm:lint:css -- --fix\"", 29 | "lint:hbs": "ember-template-lint .", 30 | "lint:js": "eslint --cache .", 31 | "start": "ember serve", 32 | "test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"", 33 | "test:ember": "ember test", 34 | "test:node": "mocha node-tests/*.js" 35 | }, 36 | "dependencies": { 37 | "@ember/edition-utils": "^1.2.0", 38 | "babel-plugin-ember-template-compilation": "^2.0.0", 39 | "broccoli-debug": "^0.6.5", 40 | "broccoli-persistent-filter": "^3.1.2", 41 | "broccoli-plugin": "^4.0.3", 42 | "fs-tree-diff": "^2.0.1", 43 | "heimdalljs-logger": "^0.1.10", 44 | "js-string-escape": "^1.0.1", 45 | "silent-error": "^1.1.1", 46 | "walk-sync": "^2.2.0" 47 | }, 48 | "devDependencies": { 49 | "@babel/core": "^7.23.6", 50 | "@babel/eslint-parser": "^7.23.3", 51 | "@babel/plugin-transform-class-properties": "^7.23.3", 52 | "@babel/plugin-proposal-decorators": "^7.23.6", 53 | "@babel/plugin-transform-runtime": "^7.13.15", 54 | "@babel/plugin-transform-typescript": "^7.10.1", 55 | "@babel/runtime": "^7.13.8", 56 | "@ember/optional-features": "^2.0.0", 57 | "@ember/test-helpers": "^3.2.1", 58 | "@embroider/test-setup": "^3.0.3", 59 | "babel-plugin-debug-macros": "^0.3.3", 60 | "broccoli-merge-trees": "^4.2.0", 61 | "broccoli-test-helper": "^2.0.0", 62 | "chai": "^4.3.4", 63 | "code-equality-assertions": "^0.9.0", 64 | "concurrently": "^8.2.2", 65 | "ember-auto-import": "^2.7.0", 66 | "ember-cli": "~5.5.0", 67 | "ember-cli-babel": "^8.2.0", 68 | "ember-cli-clean-css": "^3.0.0", 69 | "ember-cli-dependency-checker": "^3.3.2", 70 | "ember-cli-inject-live-reload": "^2.1.0", 71 | "ember-load-initializers": "^2.1.1", 72 | "ember-qunit": "^8.0.2", 73 | "ember-resolver": "^11.0.1", 74 | "ember-source": "~5.5.0", 75 | "ember-source-channel-url": "^3.0.0", 76 | "ember-styleguide": "^8.4.0", 77 | "ember-template-lint": "^5.13.0", 78 | "ember-try": "^3.0.0", 79 | "eslint": "^8.55.0", 80 | "eslint-config-prettier": "^9.1.0", 81 | "eslint-plugin-ember": "^11.11.1", 82 | "eslint-plugin-mocha": "^8.0.0", 83 | "eslint-plugin-n": "^16.4.0", 84 | "eslint-plugin-prettier": "^5.0.1", 85 | "eslint-plugin-qunit": "^8.0.1", 86 | "fixturify": "^2.1.1", 87 | "loader.js": "^4.7.0", 88 | "mocha": "^8.4.0", 89 | "module-name-inliner": "link:./tests/dummy/lib/module-name-inliner", 90 | "prettier": "^3.1.1", 91 | "qunit": "^2.20.0", 92 | "qunit-dom": "^2.0.0", 93 | "release-it": "^14.2.1", 94 | "release-it-lerna-changelog": "^3.1.0", 95 | "stylelint": "^15.11.0", 96 | "stylelint-config-standard": "^34.0.0", 97 | "stylelint-prettier": "^4.1.0", 98 | "webpack": "^5.89.0" 99 | }, 100 | "peerDependencies": { 101 | "@babel/core": ">= 7", 102 | "ember-source": ">= 4.0.0" 103 | }, 104 | "engines": { 105 | "node": ">= 18" 106 | }, 107 | "publishConfig": { 108 | "registry": "https://registry.npmjs.org" 109 | }, 110 | "ember": { 111 | "edition": "octane" 112 | }, 113 | "ember-addon": { 114 | "main": "lib/ember-addon-main.js", 115 | "configPath": "tests/dummy/config" 116 | }, 117 | "resolutions": { 118 | "ember-cli-htmlbars": "link:." 119 | }, 120 | "release-it": { 121 | "plugins": { 122 | "release-it-lerna-changelog": { 123 | "infile": "CHANGELOG.md", 124 | "launchEditor": true 125 | } 126 | }, 127 | "git": { 128 | "tagName": "v${version}" 129 | }, 130 | "github": { 131 | "release": true, 132 | "tokenRef": "GITHUB_AUTH" 133 | }, 134 | "npm": { 135 | "publish": false 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test_page: 'tests/index.html?hidepassed', 3 | disable_watching: true, 4 | launch_in_ci: ['Chrome'], 5 | launch_in_dev: ['Chrome'], 6 | browser_start_timeout: 120, 7 | browser_args: { 8 | Chrome: { 9 | ci: [ 10 | // --no-sandbox is needed when running Chrome inside a container 11 | process.env.CI ? '--no-sandbox' : null, 12 | '--headless', 13 | '--disable-dev-shm-usage', 14 | '--mute-audio', 15 | '--remote-debugging-port=0', 16 | '--window-size=1440,900', 17 | ].filter(Boolean), 18 | }, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /tests/acceptance/styleguide-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { visit, currentURL } from '@ember/test-helpers'; 3 | import { setupApplicationTest } from 'ember-qunit'; 4 | 5 | module('Acceptance | styleguide', function (hooks) { 6 | setupApplicationTest(hooks); 7 | 8 | test('visiting /styleguide', async function (assert) { 9 | await visit('/styleguide'); 10 | 11 | assert.strictEqual(currentURL(), '/styleguide'); 12 | 13 | assert.dom('[data-test-es-note-heading]').containsText('says...'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/colocation/app/components/bar.hbs: -------------------------------------------------------------------------------- 1 | Hello from bar! 2 | -------------------------------------------------------------------------------- /tests/colocation/app/components/bar.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | 3 | export default Component.extend({ 4 | attributeBindings: ['lolol'], 5 | }); 6 | -------------------------------------------------------------------------------- /tests/colocation/app/components/baz/index.hbs: -------------------------------------------------------------------------------- 1 | Module: {{module-name-inliner}} 2 | -------------------------------------------------------------------------------- /tests/colocation/app/components/foo.hbs: -------------------------------------------------------------------------------- 1 | Module: {{module-name-inliner}} 2 | -------------------------------------------------------------------------------- /tests/colocation/app/components/foo/bar.hbs: -------------------------------------------------------------------------------- 1 | Module: {{module-name-inliner}} 2 | -------------------------------------------------------------------------------- /tests/colocation/app/components/foo/baz/index.hbs: -------------------------------------------------------------------------------- 1 | Module: {{module-name-inliner}} 2 | -------------------------------------------------------------------------------- /tests/colocation/app/components/its-native.hbs: -------------------------------------------------------------------------------- 1 | {{this.greeting}} 2 | -------------------------------------------------------------------------------- /tests/colocation/app/components/its-native.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | 4 | export default class ItsNative extends Component { 5 | @tracked greeting = 'Hello!'; 6 | } 7 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | const App = Application.extend({ 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix, 9 | Resolver, 10 | }); 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/components/pods-comp/template.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ember-cli/ember-cli-htmlbars/671b3953f1c7ca5c53cfb84fe49b1cac31764c2a/tests/dummy/app/components/pods-comp/template.hbs -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dummy 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | 11 | 12 | 13 | 14 | {{content-for "head-footer"}} 15 | 16 | 17 | {{content-for "body"}} 18 | 19 | 20 | 21 | 22 | {{content-for "body-footer"}} 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/dummy/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | const Router = EmberRouter.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootURL, 7 | }); 8 | 9 | Router.map(function () { 10 | this.route('styleguide'); 11 | }); 12 | 13 | export default Router; 14 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | /* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */ 2 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

ember-cli-htmlbars

2 | Run Tests 3 | {{outlet}} -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/x-module-name-inlined-component.hbs: -------------------------------------------------------------------------------- 1 | {{module-name-inliner}} 2 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/x-module-name-reversed-component.hbs: -------------------------------------------------------------------------------- 1 | {{module-name-reverser}} 2 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/styleguide.hbs: -------------------------------------------------------------------------------- 1 | You should try out this cool note component -------------------------------------------------------------------------------- /tests/dummy/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "5.5.0", 7 | "blueprints": [ 8 | { 9 | "name": "addon", 10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output", 11 | "codemodsSource": "ember-addon-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--yarn", 15 | "--no-welcome" 16 | ] 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tests/dummy/config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); 5 | 6 | module.exports = async function () { 7 | return { 8 | useYarn: true, 9 | scenarios: [ 10 | { 11 | name: 'ember-lts-4.12', 12 | npm: { 13 | devDependencies: { 14 | 'ember-source': '~4.12.0', 15 | 'ember-cli': '~4.12.0', 16 | }, 17 | }, 18 | }, 19 | { 20 | name: 'ember-release', 21 | npm: { 22 | devDependencies: { 23 | 'ember-source': await getChannelURL('release'), 24 | '@ember/string': '^3.1.1', 25 | }, 26 | ember: { 27 | edition: 'octane', 28 | }, 29 | }, 30 | }, 31 | { 32 | name: 'ember-beta', 33 | npm: { 34 | devDependencies: { 35 | 'ember-source': await getChannelURL('beta'), 36 | '@ember/string': '^3.1.1', 37 | }, 38 | ember: { 39 | edition: 'octane', 40 | }, 41 | }, 42 | }, 43 | { 44 | name: 'ember-canary', 45 | npm: { 46 | devDependencies: { 47 | 'ember-source': await getChannelURL('canary'), 48 | '@ember/string': '^3.1.1', 49 | }, 50 | ember: { 51 | edition: 'octane', 52 | }, 53 | }, 54 | }, 55 | embroiderSafe(), 56 | embroiderOptimized(), 57 | ], 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const ENV = { 5 | modulePrefix: 'dummy', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'history', 9 | EmberENV: { 10 | EXTEND_PROTOTYPES: false, 11 | FEATURES: { 12 | // Here you can enable experimental features on an ember canary build 13 | // e.g. 'with-controller': true 14 | }, 15 | }, 16 | 17 | APP: { 18 | // Here you can pass flags/options to your application instance 19 | // when it is created 20 | }, 21 | }; 22 | 23 | if (environment === 'development') { 24 | // ENV.APP.LOG_RESOLVER = true; 25 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 26 | // ENV.APP.LOG_TRANSITIONS = true; 27 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 28 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 29 | } 30 | 31 | if (environment === 'test') { 32 | // Testem prefers this... 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | ENV.APP.autoboot = false; 41 | } 42 | 43 | if (environment === 'production') { 44 | // here you can enable a production-specific feature 45 | } 46 | 47 | return ENV; 48 | }; 49 | -------------------------------------------------------------------------------- /tests/dummy/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "jquery-integration": false, 4 | "template-only-glimmer-components": true 5 | } 6 | -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions', 7 | ]; 8 | 9 | module.exports = { 10 | browsers, 11 | }; 12 | -------------------------------------------------------------------------------- /tests/dummy/lib/module-name-inliner/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | name: require('./package').name, 5 | 6 | isDevelopingAddon() { 7 | return true; 8 | }, 9 | 10 | setupPreprocessorRegistry(type, registry) { 11 | // can only add the plugin with this style on newer Ember versions 12 | registry.add('htmlbars-ast-plugin', this.buildPlugin()); 13 | }, 14 | 15 | buildPlugin() { 16 | // https://astexplorer.net/#/gist/7c8399056873e1ddbd2b1acf0a41592a/e08f2f850de449b77b8ad995085496a1100dfd1f 17 | return { 18 | name: 'module-name-inliner', 19 | baseDir() { 20 | return __dirname; 21 | }, 22 | parallelBabel: { 23 | requireFile: __filename, 24 | buildUsing: 'buildPlugin', 25 | params: {}, 26 | }, 27 | plugin(env) { 28 | let { builders } = env.syntax; 29 | 30 | return { 31 | name: 'module-name-inliner', 32 | 33 | visitor: { 34 | PathExpression(node) { 35 | if (node.original === 'module-name-inliner') { 36 | // replacing the path with a string literal, like this 37 | // {{"the-module-name-here"}} 38 | return builders.string(env.moduleName); 39 | } 40 | }, 41 | }, 42 | }; 43 | }, 44 | }; 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /tests/dummy/lib/module-name-inliner/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "module-name-inliner", 3 | "version": "0.1.0", 4 | "keywords": [ 5 | "ember-addon" 6 | ], 7 | "dependencies": {} 8 | } 9 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/helpers/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest, 5 | } from 'ember-qunit'; 6 | 7 | // This file exists to provide wrappers around ember-qunit's 8 | // test setup functions. This way, you can easily extend the setup that is 9 | // needed per test type. 10 | 11 | function setupApplicationTest(hooks, options) { 12 | upstreamSetupApplicationTest(hooks, options); 13 | 14 | // Additional setup for application tests can be done here. 15 | // 16 | // For example, if you need an authenticated session for each 17 | // application test, you could do: 18 | // 19 | // hooks.beforeEach(async function () { 20 | // await authenticateSession(); // ember-simple-auth 21 | // }); 22 | // 23 | // This is also a good place to call test setup functions coming 24 | // from other addons: 25 | // 26 | // setupIntl(hooks); // ember-intl 27 | // setupMirage(hooks); // ember-cli-mirage 28 | } 29 | 30 | function setupRenderingTest(hooks, options) { 31 | upstreamSetupRenderingTest(hooks, options); 32 | 33 | // Additional setup for rendering tests can be done here. 34 | } 35 | 36 | function setupTest(hooks, options) { 37 | upstreamSetupTest(hooks, options); 38 | 39 | // Additional setup for unit tests can be done here. 40 | } 41 | 42 | export { setupApplicationTest, setupRenderingTest, setupTest }; 43 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dummy Tests 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | {{content-for "test-head"}} 11 | 12 | 13 | 14 | 15 | 16 | {{content-for "head-footer"}} 17 | {{content-for "test-head-footer"}} 18 | 19 | 20 | {{content-for "body"}} 21 | {{content-for "test-body"}} 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {{content-for "body-footer"}} 37 | {{content-for "test-body-footer"}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ember-cli/ember-cli-htmlbars/671b3953f1c7ca5c53cfb84fe49b1cac31764c2a/tests/integration/.gitkeep -------------------------------------------------------------------------------- /tests/integration/components/ast-plugins-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | 6 | module('tests/integration/components/ast-plugins-test', function (hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('stand alone templates have AST plugins ran', async function (assert) { 10 | await render(hbs``); 11 | 12 | assert.strictEqual( 13 | this.element.textContent.trim(), 14 | 'dummy/templates/components/x-module-name-inlined-component.hbs', 15 | ); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/integration/components/colocation-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | import { HAS_OCTANE } from '@ember/edition-fake-module'; 6 | 7 | module('tests/integration/components/test-inline-precompile', function (hooks) { 8 | if (!HAS_OCTANE) { 9 | // can only run against 3.13+ (due to colocation support) 10 | return; 11 | } 12 | 13 | setupRenderingTest(hooks); 14 | 15 | test('registered ast plugins run against colocated templates (template-only)', async function (assert) { 16 | await render(hbs``); 17 | 18 | assert.strictEqual( 19 | this.element.textContent.trim(), 20 | 'Module: dummy/components/foo.hbs', 21 | ); 22 | }); 23 | 24 | test('registered ast plugins run against nested colocated templates (template-only)', async function (assert) { 25 | await render(hbs``); 26 | 27 | assert.strictEqual( 28 | this.element.textContent.trim(), 29 | 'Module: dummy/components/foo/bar.hbs', 30 | ); 31 | }); 32 | 33 | test('registered ast plugins run against colocated template index files (template-only)', async function (assert) { 34 | await render(hbs``); 35 | 36 | assert.strictEqual( 37 | this.element.textContent.trim(), 38 | 'Module: dummy/components/baz/index.hbs', 39 | ); 40 | }); 41 | 42 | test('registered ast plugins run against nested colocated template index files (template-only)', async function (assert) { 43 | await render(hbs``); 44 | 45 | assert.strictEqual( 46 | this.element.textContent.trim(), 47 | 'Module: dummy/components/foo/baz/index.hbs', 48 | ); 49 | }); 50 | 51 | test('can invoke native class based component with decorators', async function (assert) { 52 | await render(hbs``); 53 | 54 | assert.strictEqual(this.element.textContent.trim(), 'Hello!'); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /tests/integration/components/test-inline-precompile-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import hbsOne from 'htmlbars-inline-precompile'; 5 | import hbsTwo from 'ember-cli-htmlbars-inline-precompile'; 6 | import { hbs as hbsThree } from 'ember-cli-htmlbars'; 7 | import { precompileTemplate } from '@ember/template-compilation'; 8 | 9 | module('tests/integration/components/test-inline-precompile', function (hooks) { 10 | setupRenderingTest(hooks); 11 | 12 | test('htmlbars-inline-precompile works', async function (assert) { 13 | await render(hbsOne`Wheeeee`); 14 | 15 | assert.strictEqual(this.element.textContent.trim(), 'Wheeeee'); 16 | }); 17 | 18 | test('ember-cli-htmlbars-inline-precompile works', async function (assert) { 19 | await render(hbsTwo`Wheeeee`); 20 | 21 | assert.strictEqual(this.element.textContent.trim(), 'Wheeeee'); 22 | }); 23 | 24 | test('ember-cli-htmlbars works', async function (assert) { 25 | await render(hbsThree`Wheeeee`); 26 | 27 | assert.strictEqual(this.element.textContent.trim(), 'Wheeeee'); 28 | }); 29 | 30 | test('precompileTemplate', async function (assert) { 31 | await render(precompileTemplate(`Wheeeee`, {})); 32 | 33 | assert.strictEqual(this.element.textContent.trim(), 'Wheeeee'); 34 | }); 35 | 36 | test('inline templates have AST plugins ran', async function (assert) { 37 | await render( 38 | hbsThree('{{module-name-inliner}}', { moduleName: 'hello-template.hbs' }), 39 | ); 40 | 41 | assert.strictEqual(this.element.textContent.trim(), 'hello-template.hbs'); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from '../app'; 2 | import config from '../config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import { start } from 'ember-qunit'; 5 | import * as QUnit from 'qunit'; 6 | import { setup } from 'qunit-dom'; 7 | 8 | setup(QUnit.assert); 9 | 10 | setApplication(Application.create(config.APP)); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ember-cli/ember-cli-htmlbars/671b3953f1c7ca5c53cfb84fe49b1cac31764c2a/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tsconfig.declarations.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "declarationDir": "declarations", 5 | "emitDeclarationOnly": true, 6 | "noEmit": false, 7 | "rootDir": "." 8 | }, 9 | "include": ["addon", "addon-test-support"] 10 | } 11 | --------------------------------------------------------------------------------