Giter Club home page Giter Club logo

babel-plugin-ember-template-compilation's Introduction

babel-plugin-ember-template-compilation

Build Status

Babel plugin that implements Ember's low-level template-compilation API.

Requirements

  • Node 12+ (when used in Node, but we also support in-browser usage)
  • Babel 7
  • the output works with ember-source 3.27+. For older Ember versions, see babel-plugin-htmlbars-inline-precompile instead.

Usage

This plugin implements precompileTemplate from RFC 496:

import { precompileTemplate } from '@ember/template-compilation';

For backward compatibility, it also has an enableLegacyModules option that can enable each of these widely-used older patterns:

import { hbs } from 'ember-cli-htmlbars';
import hbs from 'ember-cli-htmlbars-inline-precompile';
import hbs from 'htmlbars-inline-precompile';

Common Options

This package has both a Node implementation and a portable implementation that works in browsers.

The exported modules are:

  • babel-plugin-ember-template-compilation: automatically chooses between the node and browser implementations (using package.json exports feature).
  • babel-plugin-ember-template-compilation/browser: the core implementation that works in browsers (and anywhere else) without using any Node-specific APIs.
  • babel-plugin-ember-template-compilation/node: the Node-specific implementation that adds the ability to automatically load plugins from disk, etc.

The options are:

interface Options {
  // The ember-template-compiler.js module that ships within your ember-source version. In the browser implementation, this is mandatory. In the Node implementation you can use compilerPath instead.
  compiler?: EmberTemplateCompiler;

  // The on-disk path to the ember-template-compiler.js module for our current
  // ember version. You need to either set `compilerPath` or set `compiler`.
  // This will get resolved from the current working directory, so a package name
  // like "ember-source/dist/ember-template-compiler" is acceptable.
  compilerPath?: string;

  // Allows you to remap what imports will be emitted in our compiled output. By
  // example:
  //
  //   outputModuleOverrides: {
  //     '@ember/template-factory': {
  //       createTemplateFactory: ['createTemplateFactory', '@glimmer/core'],
  //     }
  //   }
  //
  // Normal Ember apps shouldn't need this, it exists to support other
  // environments like standalone GlimmerJS
  outputModuleOverrides?: Record<string, Record<string, [string, string]>>;

  // By default, this plugin implements only Ember's stable public API for
  // template compilation, which is:
  //
  //    import { precompileTemplate } from '@ember/template-compilation';
  //
  // But historically there are several other importable syntaxes in widespread
  // use, and we can enable those too by including their module names in this
  // list. See `type LegacyModuleName` below.
  enableLegacyModules?: LegacyModuleName[];

  // Controls the output format.
  //
  //  "wire": The default. In the output, your templates are ready to execute in
  //  the most performant way.
  //
  //  "hbs": In the output, your templates will still be in HBS format.
  //  Generally this means they will still need further processing before
  //  they're ready to execute. The purpose of this mode is to support things
  //  like codemods and pre-publication transformations in libraries.
  targetFormat?: 'wire' | 'hbs';

  // Optional list of custom transforms to apply to the handlebars AST before
  // compilation. See `type Transform` below.
  transforms?: Transform[];
}

// The legal legacy module names. These are the only ones that are supported,
// because these are the ones in widespread community use. We don't want people
// creating new ones -- prefer `@ember/template-compilation` in new code.
type LegacyModuleName =
  | 'ember-cli-htmlbars'
  | 'ember-cli-htmlbars-inline-precompile'
  | 'htmlbars-inline-precompile';

// Each transform can be
//   - the actual AST transform function (this is the only kind that works in non-Node environments)
//   - a path to a module where we will find the AST transform function as the default export
//   - an array of length two containing the path to a module and an arguments object.
//       In this case we will pass the arguments to the default export from the module and
//       it should return the actual AST transform function.
// All the path resolving happens relative to the current working directory and
// respects node_modules resolution.
type Transform = Function | string | [string, unknown];

JSUtils: Manipulating Javascript from within AST transforms

AST transforms are plugins for modifying HBS templates at build time. Because those templates are embedded in Javascript and can access the Javascript scope, an AST plugin may want to introduce some new things into Javascript scope. That is what the JSUtils API is for.

Your AST transform can access the JSUtils API via env.meta.jsutils. Here's an example transform.

function myAstTransform(env) {
  return {
    name: 'my-ast-transform',
    visitor: {
      PathExpression(node, path) {
        if (node.original === 'onePlusOne') {
          let name = env.meta.jsutils.bindExpression('1+1', path, { nameHint: 'two' });
          return env.syntax.builders.path(name);
        }
      },
    },
  };
}

The example transform above would rewrite:

import { precompileTemplate } from '@ember/template-compilation';
precompileTemplate('<Counter @value={{onePlusOne}} />>');

To:

import { precompileTemplate } from '@ember/template-compilation';
let two = 1 + 1;
precompileTemplate('<Counter @value={{two}} />', { scope: () => ({ two }) });

See the jsdoc comments in js-utils.js for details on the methods available.

Acknowledgement / History

This repo derives from https://github.com/ember-cli/babel-plugin-htmlbars-inline-precompile

babel-plugin-ember-template-compilation's People

Contributors

achambers avatar alisdair avatar bertdeblock avatar candunaj avatar chadhietala avatar chancancode avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar dfreeman avatar ef4 avatar greenkeeper[bot] avatar greenkeeperio-bot avatar jamescdavis avatar mansona avatar mixonic avatar nullvoxpopuli avatar pangratz avatar patricklx avatar runspired avatar rwjblue avatar stefanpenner avatar topaxi avatar turbo87 avatar wagenet avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

babel-plugin-ember-template-compilation's Issues

2.2.2 breaking Embroider tests

FYI my addon's Embroider tests are suddenly failing, I believe due to the 2.2.2 release of this package. I haven't investigated this issue fully, so perhaps I have something configured incorrectly but I just wanted to put this out.

Here is the full build error:

Build Error
ERROR in ./assets/dummy.js
Module build failed (from ../../thread-loader/dist/cjs.js):
Thread Loader (Worker 0)
/home/runner/work/design-system/design-system/node_modules/.embroider/rewritten-app/assets/dummy.js: when targetFormat==="wire" you must set the compiler or compilerPath option

    at normalizeOpts (/home/runner/work/design-system/design-system/node_modules/babel-plugin-ember-template-compilation/src/plugin.js:57:19)
    at PluginPass.enter (/home/runner/work/design-system/design-system/node_modules/babel-plugin-ember-template-compilation/src/plugin.js:72:48)
    at NodePath._call (/home/runner/work/design-system/design-system/node_modules/@babel/traverse/lib/path/context.js:[46](https://github.com/trusted-american/design-system/actions/runs/8727954701/job/23946690512?pr=68#step:5:47):20)
    at NodePath.call (/home/runner/work/design-system/design-system/node_modules/@babel/traverse/lib/path/context.js:36:17)
    at NodePath.visit (/home/runner/work/design-system/design-system/node_modules/@babel/traverse/lib/path/context.js:82:31)
    at TraversalContext.visitQueue (/home/runner/work/design-system/design-system/node_modules/@babel/traverse/lib/context.js:89:16)
    at TraversalContext.visitSingle (/home/runner/work/design-system/design-system/node_modules/@babel/traverse/lib/context.js:65:19)
    at TraversalContext.visit (/home/runner/work/design-system/design-system/node_modules/@babel/traverse/lib/context.js:112:19)
    at traverseNode (/home/runner/work/design-system/design-system/node_modules/@babel/traverse/lib/traverse-node.js:22:17)
    at Object.traverse (/home/runner/work/design-system/design-system/node_modules/@babel/traverse/lib/index.js:[52](https://github.com/trusted-american/design-system/actions/runs/8727954701/job/23946690512?pr=68#step:5:53):34)
    at PluginPass.pre (/home/runner/work/design-system/design-system/node_modules/babel-plugin-ember-template-compilation/src/plugin.js:170:23)
    at PluginPass.<anonymous> (/home/runner/work/design-system/design-system/node_modules/@embroider/core/src/portable-babel-launcher.js:31:33)
    at transformFile (/home/runner/work/design-system/design-system/node_modules/@babel/core/lib/transformation/index.js:73:27)
    at transformFile.next (<anonymous>)
    at run (/home/runner/work/design-system/design-system/node_modules/@babel/core/lib/transformation/index.js:24:12)
    at run.next (<anonymous>)
    at transform (/home/runner/work/design-system/design-system/node_modules/@babel/core/lib/transform.js:22:33)
    at transform.next (<anonymous>)
    at step (/home/runner/work/design-system/design-system/node_modules/gensync/index.js:261:32)
    at /home/runner/work/design-system/design-system/node_modules/gensync/index.js:273:13
    at async.call.result.err.err (/home/runner/work/design-system/design-system/node_modules/gensync/index.js:223:11)
    at /home/runner/work/design-system/design-system/node_modules/gensync/index.js:189:28
    at /home/runner/work/design-system/design-system/node_modules/@babel/core/lib/gensync-utils/async.js:67:7
    at /home/runner/work/design-system/design-system/node_modules/gensync/index.js:113:33
    at step (/home/runner/work/design-system/design-system/node_modules/gensync/index.js:287:14)
    at /home/runner/work/design-system/design-system/node_modules/gensync/index.js:273:13
    at async.call.result.err.err (/home/runner/work/design-system/design-system/node_modules/gensync/index.js:223:11)

Instructions unclear, got precompile is not a function.

babel config
import { createRequire } from 'module';

const require = createRequire(import.meta.url);
const resolve = require.resolve;

export default {
  plugins: [
    [
      resolve('@babel/plugin-transform-typescript'),
      {
        allowDeclareFields: true,
        onlyRemoveTypeImports: true,
        // Default enums are IIFEs
        optimizeConstEnums: true,
      },
    ],
    [
      resolve('@babel/plugin-proposal-class-properties'),
      {
        // Only support browsers that also support class properties...
        // If all addons do this, it greatly reduces shipped JS
        loose: true,
      },
    ],
    [resolve('babel-plugin-ember-template-compilation'), {
      enableLegacyModules: [
        'ember-cli-htmlbars',
      ]
    }],
    resolve('@embroider/addon-dev/template-colocation-plugin'),
  ],
};
rollup config
import path from 'path';
import { nodeResolve } from '@rollup/plugin-node-resolve';

import { babel } from '@rollup/plugin-babel';

import { Addon } from '@embroider/addon-dev/rollup';

const addon = new Addon({
  srcDir: 'src',
  destDir: 'dist',
});

const extensions = ['.js', '.ts'];

const rollupConfig = {
  input: [path.join('src', 'index.ts'), path.join('src', 'registration.ts')],

  // This provides defaults that work well alongside `publicEntrypoints` below.
  // You can augment this if you need to.
  output: { ...addon.output(), entryFileNames: '[name].js' },

  plugins: [
    // this is needed so we can have files that import other files...
    nodeResolve({ resolveOnly: ['./'], extensions }),

    // These are the modules that users should be able to import from your
    // addon. Anything not listed here may get optimized away.
    addon.publicEntrypoints(['index.js', 'registration.js']),

    // These are the modules that should get reexported into the traditional
    // "app" tree. Things in here should also be in publicEntrypoints above, but
    // not everything in publicEntrypoints necessarily needs to go here.
    // addon.appReexports(['components/**/*.js', 'services/**/*.js']),

    // This babel config should *not* apply presets or compile away ES modules.
    // It exists only to provide development niceties for you, like automatic
    // template colocation.
    // See `babel.config.json` for the actual Babel configuration!
    babel({ babelHelpers: 'bundled', extensions }),

    // Follow the V2 Addon rules about dependencies. Your code can import from
    // `dependencies` and `peerDependencies` as well as standard Ember-provided
    // package names.
    addon.dependencies(),

    // Ensure that standalone .hbs files are properly integrated as Javascript.
    addon.hbs(),

    // addons are allowed to contain imports of .css files, which we want rollup
    // to leave alone and keep in the published output.
    // addon.keepAssets(['**/*.css']),

    // Remove leftover build artifacts when starting a new build.
    addon.clean(),
  ],
};

export default rollupConfig;

Error:

[build:js] [!] (plugin babel) TypeError: /✂️/ember-statechart-component/ember-statechart-component/src/registration.ts: precompile is not a function
[build:js] src/registration.ts
[build:js] TypeError: /✂️/ember-statechart-component/ember-statechart-component/src/registration.ts: precompile is not a function
[build:js]     at insertCompiledTemplate (/✂️/ember-statechart-component/node_modules/babel-plugin-ember-template-compilation/src/plugin.ts:107:34)
[build:js]     at PluginPass.TaggedTemplateExpression (/✂️/ember-statechart-component/node_modules/babel-plugin-ember-template-compilation/src/plugin.ts:177:11)
[build:js]     at newFn (/✂️/ember-statechart-component/node_modules/@babel/traverse/lib/visitors.js:177:21)
[build:js]     at NodePath._call (/✂️/ember-statechart-component/node_modules/@babel/traverse/lib/path/context.js:53:20)

Code that was trying to be transpiled:

import { setComponentManager, setComponentTemplate } from '@ember/component';
import { hbs } from 'ember-cli-htmlbars';
// ...
setComponentTemplate(hbs`{{yield this.state this.send}}`, StateNode.prototype);

All options properties in the README say "optional" (?:)

.gts test files not working in 2.2.2

Since the upgrade to babel-plugin-ember-template-compilation version 2.2.2, my test files with template imports (gts files) are not loading anymore.

In QUnit I'm getting 1 global failure:

1. Error: 
Error: Could not find module `ember-template-compiler` imported from `@ember/template-compilation/index`
TypeError: Cannot read properties of undefined (reading 'exports')
TypeError: Cannot read properties of undefined (reading 'exports')
TypeError: Cannot read properties of undefined (reading 'exports')
TypeError: Cannot read properties of undefined (reading 'exports')
TypeError: Cannot read properties of undefined (reading 'exports')
Source: 	
Error: 
Error: Could not find module `ember-template-compiler` imported from `@ember/template-compilation/index`
TypeError: Cannot read properties of undefined (reading 'exports')
TypeError: Cannot read properties of undefined (reading 'exports')
TypeError: Cannot read properties of undefined (reading 'exports')
TypeError: Cannot read properties of undefined (reading 'exports')
TypeError: Cannot read properties of undefined (reading 'exports')
    at eval (webpack://__ember_auto_import__/./node_modules/ember-qunit/dist/test-loader.js?:11:62)
    at eval (webpack://__ember_auto_import__/./node_modules/qunit/qunit/qunit.js?:376:140)

and all my gts test files have an error too:

1. Died on test #1: Cannot read properties of undefined (reading 'exports')
    at TestLoader.moduleLoadFailure (webpack://__ember_auto_import__/./node_modules/ember-qunit/dist/test-loader.js?:12:301)
    at TestLoader.require (http://localhost:4304/assets/test-support.js:7438:14)
    at TestLoader.loadModules (http://localhost:4304/assets/test-support.js:7430:14)
    at loadTests (webpack://__ember_auto_import__/./node_modules/ember-qunit/dist/test-loader.js?:25:42)
    at start (webpack://__ember_auto_import__/./node_modules/ember-qunit/dist/index.js?:83:119)
    at Module.callback (http://localhost:4304/assets/tests.js:4831:25)
    at Module.exports (http://localhost:4304/assets/vendor.js:125:32)@ 0 ms
Source: 	
TypeError: Cannot read properties of undefined (reading 'exports')
    at Module._reify (http://localhost:4304/assets/vendor.js:162:59)
    at Module.reify (http://localhost:4304/assets/vendor.js:149:27)
    at Module.exports (http://localhost:4304/assets/vendor.js:123:10)
    at Module._reify (http://localhost:4304/assets/vendor.js:162:59)
    at Module.reify (http://localhost:4304/assets/vendor.js:149:27)
    at Module.exports (http://localhost:4304/assets/vendor.js:123:10)
    at requireModule (http://localhost:4304/assets/vendor.js:46:18)
    at TestLoader.require (http://localhost:4304/assets/test-support.js:7436:9)
    at TestLoader.loadModules (http://localhost:4304/assets/test-support.js:7430:14)
    at loadTests (webpack://__ember_auto_import__/./node_modules/ember-qunit/dist/test-loader.js?:25:42)

or

1. Died on test #1: Could not find module `ember-template-compiler` imported from `@ember/template-compilation/index`
    at TestLoader.moduleLoadFailure (webpack://__ember_auto_import__/./node_modules/ember-qunit/dist/test-loader.js?:12:301)
    at TestLoader.require (http://localhost:4304/assets/test-support.js:7438:14)
    at TestLoader.loadModules (http://localhost:4304/assets/test-support.js:7430:14)
    at loadTests (webpack://__ember_auto_import__/./node_modules/ember-qunit/dist/test-loader.js?:25:42)
    at start (webpack://__ember_auto_import__/./node_modules/ember-qunit/dist/index.js?:83:119)
    at Module.callback (http://localhost:4304/assets/tests.js:4831:25)
    at Module.exports (http://localhost:4304/assets/vendor.js:125:32)@ 0 ms
Source: 	
Error: Could not find module `ember-template-compiler` imported from `@ember/template-compilation/index`
    at missingModule (http://localhost:4304/assets/vendor.js:266:11)
    at findModule (http://localhost:4304/assets/vendor.js:277:7)
    at Module.findDeps (http://localhost:4304/assets/vendor.js:187:24)
    at findModule (http://localhost:4304/assets/vendor.js:281:11)
    at Module.findDeps (http://localhost:4304/assets/vendor.js:187:24)
    at findModule (http://localhost:4304/assets/vendor.js:281:11)
    at requireModule (http://localhost:4304/assets/vendor.js:43:15)
    at TestLoader.require (http://localhost:4304/assets/test-support.js:7436:9)
    at TestLoader.loadModules (http://localhost:4304/assets/test-support.js:7430:14)
    at loadTests (webpack://__ember_auto_import__/./node_modules/ember-qunit/dist/test-loader.js?:25:42)

Potential issue with 2.0.1

EmberData's no-lockfile test scenario hits this issue when picking up the new release:

tests/request test:   - errorType: [undefined]
tests/request test:   - location:
tests/request test:     - column: [undefined]
tests/request test:     - file: [undefined]
tests/request test:     - line: [undefined]
tests/request test:   - message: Cannot find module 'babel-plugin-ember-template-compilation'
tests/request test: Require stack:
tests/request test: - /home/runner/work/data/data/node_modules/.pnpm/[email protected][email protected]/node_modules/ember-auto-import/js/package.js
tests/request test: - /home/runner/work/data/data/node_modules/.pnpm/[email protected][email protected]/node_modules/ember-auto-import/js/auto-import.js
tests/request test: - /home/runner/work/data/data/node_modules/.pnpm/[email protected][email protected]/node_modules/ember-auto-import/js/index.js

2.2.2 breaking change: 'modifier' keyword is now overridden by local state

This worked under 2.2.1:

import { modifier } from "ember-modifier";

const addClass = modifier((el) => {
  el.classList.add("foo");
});

const shouldAddClass = true;

<template>
  <div {{(if shouldAddClass (modifier addClass))}}>
  </div>
</template>

modifier in the template would refer to the built-in modifier helper, not the modifier function in the local scope.

Under 2.2.2, the template now takes modifier from the local scope, which (understandably) leads to some very strange errors.

We can work around the issue by changing the name of the import like

import { modifier as renamedModifier } from "ember-modifier";

so that it doesn't clash with the builtin.

Clearly the old behaviour was quite 'magic'... so changing the behaviour here in a major version seems reasonable. Perhaps the modifier helper should always be explicitly imported, rather than being a built-in? But having this behaviour change as part of a patch version bump is not ideal.

2.2.2 breaking change: globals are now 'not in scope' for templates?

There appears to be a breaking change between 2.2.1 and 2.2.2. Previously, in gjs files, we could reference JS globals directly as helpers. For example, we were using @something=(Boolean this.blah) to cast a value to a boolean.

Now, we get the error:

Error: Attempted to resolve a helper in a strict mode template, but that value was not in scope: Boolean

Adding const Boolean = window.Boolean to the file works around the problem.

This problem is not limited to helpers. Something like {{document.title}} previously worked, but now throws a 'not in scope' error.

Reason for only allowing direct references to in-scope values?

This assertion seems to allow only scopes defined as { foo } or { foo: foo }, but not { foo: bar }. I wonder why that is?

The problem I was running into:

I am using rollup-plugin-glimmer-template-tag and rollup-plugin-ts in a v2 addon, I was importing the default export of a component HeadlessFormFieldComponent under a shorter name, like this:

import FieldComponent from './field';

But during the compilation process (/dist output of v2 addon), this gets implicitly transformed to something like this:

import HeadlessFormFieldComponent from './field';

setComponentTemplate(precompileTemplate(`...`, {
  strictMode: true,
  scope: () => ({
    FieldComponent: HeadlessFormFieldComponent
  })

Idk why this is being done, but it seems perfectly valid from a pure JS standpoint. But the babel plugin then throws Scope objects for precompileTemplate may only contain direct references to in-scope values, e.g. { FieldComponent } or { FieldComponent: FieldComponent }

Similar things happen when you import by assigning an alias, like

import { EnsureSafeComponentHelper as ensureSafeComponent } from '@embroider/util';

...which gets rewritten to

import { EnsureSafeComponentHelper } from '@embroider/util';

setComponentTemplate(precompileTemplate(`...`, {
  strictMode: true,
  scope: () => ({
    ensureSafeComponent: EnsureSafeComponentHelper
  })

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.