@norith/glimmerx-babel-preset 1.0.8 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -2,6 +2,20 @@ function defaultTo(value, defaultVal) {
2
2
  return value === undefined ? defaultVal : value;
3
3
  }
4
4
 
5
+ // The preserve-scope plugin MUST be added as a top-level plugin in the
6
+ // consumer's babel config, not inside this preset. See
7
+ // ./preserve-scope-plugin.js for the reason. We cannot wire it in here
8
+ // because presets expand in reverse array order — any plugin inside this
9
+ // preset would run AFTER @babel/preset-typescript's import stripper, which
10
+ // is too late to prevent template-only imports from being removed.
11
+ //
12
+ // Consumers must add it like so:
13
+ //
14
+ // plugins: [
15
+ // require('@glimmerx/babel-preset/preserve-scope-plugin'),
16
+ // // ...their other plugins
17
+ // ],
18
+
5
19
  module.exports = function (api, options) {
6
20
  let __loadPlugins = defaultTo(options.__loadPlugins, false);
7
21
 
@@ -44,3 +58,9 @@ module.exports = function (api, options) {
44
58
  ],
45
59
  };
46
60
  };
61
+
62
+ // Named re-export so consumers can do:
63
+ // const { preserveScopePlugin } = require('@glimmerx/babel-preset');
64
+ // as an alternative to:
65
+ // require('@glimmerx/babel-preset/preserve-scope-plugin')
66
+ module.exports.preserveScopePlugin = require('./preserve-scope-plugin');
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@norith/glimmerx-babel-preset",
3
- "version": "1.0.8",
3
+ "version": "1.0.11",
4
4
  "description": "GlimmerX Babel Preset",
5
- "repository": "https://github.com/snorith/glimmer-experimental",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/snorith/glimmer-experimental"
8
+ },
6
9
  "author": "Tom Dale <tom@tomdale.net>",
7
10
  "license": "MIT",
8
11
  "private": false,
@@ -10,7 +13,7 @@
10
13
  "test": "mocha -r esm"
11
14
  },
12
15
  "dependencies": {
13
- "@norith/glimmer-babel-preset": "^1.0.1"
16
+ "@norith/glimmer-babel-preset": "^1.0.2"
14
17
  },
15
18
  "devDependencies": {
16
19
  "babel-plugin-tester": "^6.4.0",
@@ -0,0 +1,63 @@
1
+ // Preserves top-level bindings through the babel pass so that other transforms
2
+ // (notably @babel/preset-typescript's unused-import stripper) do not remove
3
+ // imports that are referenced only from inside an `hbs` tagged template string.
4
+ //
5
+ // Babel's scope tracker cannot see identifiers inside template literal strings,
6
+ // so a bare `import X from './X'` whose only use is `hbs\`<X />\`` looks
7
+ // unused to preset-typescript and gets stripped. By the time
8
+ // babel-plugin-htmlbars-inline-precompile walks the template and calls
9
+ // getScope(path.scope), the binding is already gone — the template compiles
10
+ // with no locals and fails with `not in scope in a strict mode template`.
11
+ //
12
+ // This plugin runs first in the pass. On Program.enter it calls
13
+ // binding.reference(noop) for every top-level binding, which bumps each
14
+ // binding's reference count to >= 1 from preset-typescript's perspective, so
15
+ // preset-typescript leaves the imports alone. By the time
16
+ // babel-plugin-htmlbars-inline-precompile visits the `hbs` tag and emits the
17
+ // scope closure (`scope: () => [X, Y]`), it sees the bindings intact and
18
+ // references them for real inside the closure. On Program.exit we remove the
19
+ // phantom references and the noop placeholder — the real references inside
20
+ // the scope closure remain and carry the imports through tree-shaking.
21
+ //
22
+ // Ported from the removed @glimmerx/babel-plugin-component-templates (which
23
+ // used the same mechanism until the upstream consolidation around
24
+ // babel-plugin-htmlbars-inline-precompile dropped it).
25
+
26
+ module.exports = function preserveScopePlugin({ types: t }) {
27
+ return {
28
+ name: '@norith/glimmerx-babel-preset/preserve-scope',
29
+ visitor: {
30
+ Program: {
31
+ enter(path, state) {
32
+ const parentScope = path.scope.getProgramParent();
33
+
34
+ // Skip TS type-only bindings — we don't need to keep those alive and
35
+ // marking them would interfere with preset-typescript's type-only
36
+ // import handling.
37
+ const bindings = Object.values(parentScope.bindings).filter(
38
+ (b) => !b.referencePaths.some((p) => p.parent && p.parent.type === 'TSTypeReference')
39
+ );
40
+
41
+ if (bindings.length === 0) return;
42
+
43
+ // Insert a noop at the top of the program to anchor the phantom refs.
44
+ let firstNode = path.get('body.0');
45
+ firstNode.insertBefore(t.noop());
46
+ firstNode = path.get('body.0');
47
+
48
+ bindings.forEach((b) => b.reference(firstNode));
49
+
50
+ state.originalBindings = bindings;
51
+ state.emptyPath = firstNode;
52
+ },
53
+
54
+ exit(path, state) {
55
+ if (state.originalBindings) {
56
+ state.originalBindings.forEach((b) => b.dereference(state.emptyPath));
57
+ state.emptyPath.remove();
58
+ }
59
+ },
60
+ },
61
+ },
62
+ };
63
+ };
@@ -0,0 +1,16 @@
1
+ // Reproduces the consumer pipeline that triggered the bug:
2
+ // @babel/preset-typescript strips PlainAwait/on (referenced only inside an
3
+ // `hbs` tagged template literal) before babel-plugin-htmlbars-inline-precompile
4
+ // can extract the template scope. The preserveScopePlugin top-level plugin
5
+ // fixes this by phantom-referencing every top-level binding through the pass.
6
+ const path = require('path');
7
+
8
+ module.exports = {
9
+ plugins: [
10
+ require(path.resolve(__dirname, '../../../preserve-scope-plugin')),
11
+ ],
12
+ presets: [
13
+ ['@glimmerx/babel-preset', { isDebug: false }],
14
+ ['@babel/preset-typescript', { allowDeclareFields: true }],
15
+ ],
16
+ };
@@ -0,0 +1,8 @@
1
+ import Component, { hbs } from '@norith/glimmerx-component';
2
+ import { on } from '@norith/glimmerx-modifier';
3
+ import PlainAwait from 'components/PlainAwait';
4
+
5
+ export default class Foo extends Component {
6
+ go() {}
7
+ static template = hbs`<PlainAwait /><button {{on 'click' this.go}}>Go</button>`;
8
+ }
@@ -0,0 +1,19 @@
1
+ import { setComponentTemplate as _setComponentTemplate } from "@norith/glimmer-core";
2
+ import { createTemplateFactory as _createTemplateFactory } from "@norith/glimmer-core";
3
+ import Component from '@norith/glimmerx-component';
4
+ import { on } from '@norith/glimmerx-modifier';
5
+ import PlainAwait from 'components/PlainAwait';
6
+ export default class Foo extends Component {
7
+ go() {}
8
+ }
9
+ _setComponentTemplate(_createTemplateFactory(
10
+ /*
11
+ <PlainAwait /><button {{on 'click' this.go}}>Go</button>
12
+ */
13
+ {
14
+ "id": "3B6tIOMC",
15
+ "block": "[[[8,[32,0],null,null,null],[11,\"button\"],[4,[32,1],[\"click\",[30,0,[\"go\"]]],null],[12],[1,\"Go\"],[13]],[],false,[]]",
16
+ "moduleName": "(unknown template module)",
17
+ "scope": () => [PlainAwait, on],
18
+ "isStrictMode": true
19
+ }), Foo);