diff --git a/plugins/compiler.ts b/plugins/compiler.ts index 2120238e..4f0cef9d 100644 --- a/plugins/compiler.ts +++ b/plugins/compiler.ts @@ -2,7 +2,7 @@ import { type Plugin } from 'vite'; import { Preprocessor } from 'content-tag'; import { transform } from './test'; import { MAIN_IMPORT } from './symbols'; -import { flags } from './flags.ts'; +import { type Flags, defaultFlags } from './flags.ts'; import { HMR, fixExportsForHMR, shouldHotReloadFile } from './hmr.ts'; const p = new Preprocessor(); @@ -28,10 +28,12 @@ const scriptFileRegex = /\.(ts|js)$/; type Options = { authorMode?: boolean; disableHMR?: boolean; + flags?: Partial; }; export function compiler(mode: string, options: Options = {}): Plugin { let isLibBuild = false; + let flags = defaultFlags(); return { enforce: 'pre', name: 'glimmer-next', @@ -39,6 +41,7 @@ export function compiler(mode: string, options: Options = {}): Plugin { if (options.authorMode === true) { isLibBuild = config.build?.lib !== undefined; } + flags = { ...flags, ...(options.flags ?? {}) }; const defineValues: Record = flags; if (!isLibBuild) { defineValues['IS_DEV_MODE'] = mode.mode === 'development'; @@ -63,6 +66,7 @@ export function compiler(mode: string, options: Options = {}): Plugin { file, mode as 'development' | 'production', isLibBuild, + flags, ); } else { return transform( @@ -70,6 +74,7 @@ export function compiler(mode: string, options: Options = {}): Plugin { file, mode as 'development' | 'production', isLibBuild, + flags, ); } } @@ -83,6 +88,8 @@ export function compiler(mode: string, options: Options = {}): Plugin { source, file, mode as 'development' | 'production', + false, + flags, ); return result; } diff --git a/plugins/converter.test.ts b/plugins/converter.test.ts index c6c41b85..f26aca16 100644 --- a/plugins/converter.test.ts +++ b/plugins/converter.test.ts @@ -1,11 +1,13 @@ -import { expect, test, describe } from 'vitest'; +import { expect, test, describe, beforeAll } from 'vitest'; import { preprocess } from '@glimmer/syntax'; import { ComplexJSType, convert } from './converter'; import { ASTv1 } from '@glimmer/syntax'; import { HBSControlExpression, HBSNode } from './utils'; import { EVENT_TYPE } from './symbols'; -import { flags } from './flags'; +import { defaultFlags } from './flags'; + +const flags = defaultFlags(); function $glimmerCompat(str: string) { if (flags.IS_GLIMMER_COMPAT_MODE) { @@ -17,7 +19,7 @@ function $glimmerCompat(str: string) { function $t(tpl: string): ComplexJSType { const seenNodes: Set = new Set(); - const { ToJSType } = convert(seenNodes); + const { ToJSType } = convert(seenNodes, flags); const ast = preprocess(tpl); const node = ast.body[0] as T; return ToJSType(node); @@ -53,377 +55,389 @@ function $node(partial: Partial): HBSNode { }; } -describe('convert function builder', () => { - describe('basic element helper support', () => { - test('it return kinda valid component-like code', () => { - expect($t(`{{(element "tag")}}`)).toEqual( - `$:() => $:function(args,props){const $slots = $_GET_SLOTS(this, arguments);return{[$nodes]:[$_tag("tag",[props[$propsProp],props[$attrsProp],props[$eventsProp]],[()=>$_slot('default',()=>[],$slots)], this)[$node]],[$slotsProp]:$slots,index:0, ctx: this};}`, - ); - }); - }); - describe('Builtin helpers in MustacheStatements', () => { - test('fn helper properly mapped', () => { - expect($t(`{{fn a "b" "c"}}`)).toEqual( - `$:() => $:$__fn(a,"b","c")`, - ); - }); - test('if helper properly mapped', () => { - expect($t(`{{if foo "bar" "baz"}}`)).toEqual( - `$:() => $:$__if(foo,"bar","baz")`, - ); - }); - test('unless helper properly mapped', () => { - expect($t(`{{unless foo "bar" "baz"}}`)).toEqual( - `$:() => $:$__if(foo,"baz","bar")`, - ); - }); - test('eq helper properly mapped', () => { - expect($t(`{{eq foo "bar" "baz"}}`)).toEqual( - `$:() => $:$__eq(foo,"bar","baz")`, - ); - }); - test('debugger helper properly mapped', () => { - expect( - $t(`{{debugger foo "bar" "baz"}}`), - ).toEqual(`$:() => $:$__debugger.call(this,foo,"bar","baz")`); - }); - test('log helper properly mapped', () => { - expect($t(`{{log foo "bar" "baz"}}`)).toEqual( - `$:() => $:$__log(foo,"bar","baz")`, - ); - }); - test('array helper properly mapped', () => { - expect($t(`{{array foo "bar" "baz"}}`)).toEqual( - `$:() => $:$__array(foo,"bar","baz")`, - ); - }); - test('hash helper properly mapped', () => { - expect( - $t(`{{hash foo="bar" boo="baz"}}`), - ).toEqual(`$:() => $:$__hash({foo: "bar", boo: "baz"})`); - }); - }); - describe('Builtin helpers in SubExpression', () => { - test('fn helper properly mapped', () => { - expect($t(`{{q (fn a b (if c d))}}`)).toEqual( - `$:() => $:q($__fn($:a,$:b,$:$__if($:c,$:d)))`, - ); - }); - test('if helper properly mapped', () => { - expect($t(`{{q (if a b (if c d))}}`)).toEqual( - `$:() => $:q($__if($:a,$:b,$:$__if($:c,$:d)))`, - ); - }); - test('unless helper properly mapped', () => { - expect( - $t(`{{q (unless a b (if c d))}}`), - ).toEqual(`$:() => $:q($__if($:a,$:$__if($:c,$:d),$:b))`); - }); - test('eq helper properly mapped', () => { - expect($t(`{{q (eq a b)}}`)).toEqual( - `$:() => $:q($__eq($:a,$:b))`, - ); - }); - test('debugger helper properly mapped', () => { - expect($t(`{{q (debugger a)}}`)).toEqual( - `$:() => $:q($__debugger.call($:this,$:a))`, - ); - }); - test('log helper properly mapped', () => { - expect($t(`{{q (log a b)}}`)).toEqual( - `$:() => $:q($__log($:a,$:b))`, - ); - }); - test('array helper properly mapped', () => { - expect( - $t(`{{q (array foo "bar" "baz")}}`), - ).toEqual(`$:() => $:q($__array($:foo,"bar","baz"))`); - }); - test('hash helper properly mapped', () => { - expect( - $t(`{{q (hash foo="bar" boo="baz")}}`), - ).toEqual(`$:() => $:q($__hash({foo: "bar", boo: "baz"}))`); - }); - }); - describe('TextNode', () => { - test('converts a simple string', () => { - expect($t(`"Hello World"`)).toEqual(`"Hello World"`); - }); - test('non empty text nodes not trimmed', () => { - expect($t(` foo`)).toEqual(` foo`); - }); - test('non empty text nodes trimmed', () => { - expect($t(` `)).toEqual(null); - }); +describe.each([ + { glimmerCompat: true, name: 'glimmer compat mode' }, + { glimmerCompat: false, name: 'glimmer non-compat mode' }, +])('$name', ({ glimmerCompat }) => { + beforeAll(() => { + flags.IS_GLIMMER_COMPAT_MODE = glimmerCompat; }); - describe('MustacheStatement', () => { - test('converts a args-less path', () => { - expect($t(`{{foo-bar}}`)).toEqual(`$:foo-bar`); - }); - test('converts a path with args', () => { - expect($t(`{{foo-bar bas boo}}`)).toEqual( - `$:() => $:foo-bar(bas,boo)`, - ); - }); - test('converts sub-expression without args', () => { - expect($t(`{{(foo-bar)}}`)).toEqual( - `$:() => $:foo-bar()`, - ); - }); - test('supports helper composition', () => { - expect($t(`{{(foo-bar (baz-bat))}}`)).toEqual( - `$:() => $:foo-bar($:baz-bat())`, - ); - }); - test('support boolean literals', () => { - expect($t(`{{true}}`)).toEqual(true); - expect($t(`{{false}}`)).toEqual(false); - }); - test('support null literals', () => { - expect($t(`{{null}}`)).toEqual(null); - }); - test('support undefined literals', () => { - expect($t(`{{undefined}}`)).toEqual(undefined); - }); - test('support bool, null, undefined as helper args', () => { - expect( - $t(`{{foo true null undefined}}`), - ).toEqual(`$:() => $:foo(true,null,undefined)`); - }); - }); - describe('ElementNode', () => { - test('converts a simple element', () => { - expect($t(`
`)).toEqual( - $node({ tag: 'div' }), - ); - }); - test('converts a simple element with string attribute', () => { - expect($t(`
`)).toEqual( - $node({ - tag: 'div', - properties: [['className', 'foo']], - }), - ); - }); - test('converts a simple element with string child', () => { - expect($t(`
sd
`)).toEqual( - $node({ - tag: 'div', - events: [[EVENT_TYPE.TEXT_CONTENT, ' sd']], - }), - ); - }); - test('converts a simple element with complex child', () => { - expect($t(`
sd
`)).toEqual( - $node({ - tag: 'div', - children: [' sd', $node({ tag: 'span' })], - }), - ); - }); - test('converts a simple element with concat string attribute', () => { - expect( - $t(`
`), - ).toEqual( - $node({ - tag: 'div', - properties: [ - ['className', '$:() => [$:foo," bar ",$:boo(baks)].join(\'\')'], - ], - }), - ); - }); - test('converts a simple element with path attribute', () => { - expect($t(`
`)).toEqual( - $node({ - tag: 'div', - properties: [['className', '$:foo']], - }), - ); - }); - test('converts a simple element with path attribute with string literal', () => { - expect($t(`
`)).toEqual( - $node({ - tag: 'div', - properties: [['className', '$:() => $:foo("bar")']], - }), - ); - }); - test('converts a simple element with path attribute with path literal', () => { - expect($t(`
`)).toEqual( - $node({ - tag: 'div', - properties: [['className', '$:() => $:foo(bar)']], - }), - ); - }); - test('converts a simple element with `on` modifier', () => { - // @todo - likely need to return proper closure here (arrow function) - expect($t(`
`)).toEqual( - $node({ - tag: 'div', - events: [['click', '$:($e, $n) => $:foo($e, $n, )']], - }), - ); - }); - test('converts a simple element with `on` modifier, with composed args', () => { - // @todo - likely need to return proper closure here (arrow function) - expect( - $t(`
`), - ).toEqual( - $node({ - tag: 'div', - events: [['click', '$:($e, $n) => $:foo($:bar,$:baz)($e, $n, )']], - }), - ); - }); - test('support custom modifiers', () => { - expect($t(`
`)).toEqual( - $node({ - tag: 'div', - events: [['0', '$:($n) => $:foo-bar($n, )']], - }), - ); - }); - }); - describe('if condition', () => { - test('only true part', () => { - expect( - $t(`{{#if foo}}123{{/if}}`), - ).toEqual( - $control({ - condition: $glimmerCompat('$:foo'), - children: ['123'], - }), - ); - }); + describe('convert function builder', () => { + describe('basic element helper support', () => { + test('it return kinda valid component-like code', () => { + expect($t(`{{(element "tag")}}`)).toEqual( + `$:() => $:function(args,props){const $slots = $_GET_SLOTS(this, arguments);return{[$nodes]:[$_tag("tag",[props[$propsProp],props[$attrsProp],props[$eventsProp]],[()=>$_slot('default',()=>[],$slots)], this)[$node]],[$slotsProp]:$slots,index:0, ctx: this};}`, + ); + }); + }); + describe('Builtin helpers in MustacheStatements', () => { + test('fn helper properly mapped', () => { + expect($t(`{{fn a "b" "c"}}`)).toEqual( + `$:() => $:$__fn(a,"b","c")`, + ); + }); + test('if helper properly mapped', () => { + expect($t(`{{if foo "bar" "baz"}}`)).toEqual( + `$:() => $:$__if(foo,"bar","baz")`, + ); + }); + test('unless helper properly mapped', () => { + expect( + $t(`{{unless foo "bar" "baz"}}`), + ).toEqual(`$:() => $:$__if(foo,"baz","bar")`); + }); + test('eq helper properly mapped', () => { + expect($t(`{{eq foo "bar" "baz"}}`)).toEqual( + `$:() => $:$__eq(foo,"bar","baz")`, + ); + }); + test('debugger helper properly mapped', () => { + expect( + $t(`{{debugger foo "bar" "baz"}}`), + ).toEqual(`$:() => $:$__debugger.call(this,foo,"bar","baz")`); + }); + test('log helper properly mapped', () => { + expect($t(`{{log foo "bar" "baz"}}`)).toEqual( + `$:() => $:$__log(foo,"bar","baz")`, + ); + }); + test('array helper properly mapped', () => { + expect( + $t(`{{array foo "bar" "baz"}}`), + ).toEqual(`$:() => $:$__array(foo,"bar","baz")`); + }); + test('hash helper properly mapped', () => { + expect( + $t(`{{hash foo="bar" boo="baz"}}`), + ).toEqual(`$:() => $:$__hash({foo: "bar", boo: "baz"})`); + }); + }); + describe('Builtin helpers in SubExpression', () => { + test('fn helper properly mapped', () => { + expect($t(`{{q (fn a b (if c d))}}`)).toEqual( + `$:() => $:q($__fn($:a,$:b,$:$__if($:c,$:d)))`, + ); + }); + test('if helper properly mapped', () => { + expect($t(`{{q (if a b (if c d))}}`)).toEqual( + `$:() => $:q($__if($:a,$:b,$:$__if($:c,$:d)))`, + ); + }); + test('unless helper properly mapped', () => { + expect( + $t(`{{q (unless a b (if c d))}}`), + ).toEqual(`$:() => $:q($__if($:a,$:$__if($:c,$:d),$:b))`); + }); + test('eq helper properly mapped', () => { + expect($t(`{{q (eq a b)}}`)).toEqual( + `$:() => $:q($__eq($:a,$:b))`, + ); + }); + test('debugger helper properly mapped', () => { + expect($t(`{{q (debugger a)}}`)).toEqual( + `$:() => $:q($__debugger.call($:this,$:a))`, + ); + }); + test('log helper properly mapped', () => { + expect($t(`{{q (log a b)}}`)).toEqual( + `$:() => $:q($__log($:a,$:b))`, + ); + }); + test('array helper properly mapped', () => { + expect( + $t(`{{q (array foo "bar" "baz")}}`), + ).toEqual(`$:() => $:q($__array($:foo,"bar","baz"))`); + }); + test('hash helper properly mapped', () => { + expect( + $t(`{{q (hash foo="bar" boo="baz")}}`), + ).toEqual(`$:() => $:q($__hash({foo: "bar", boo: "baz"}))`); + }); + }); + describe('TextNode', () => { + test('converts a simple string', () => { + expect($t(`"Hello World"`)).toEqual(`"Hello World"`); + }); + test('non empty text nodes not trimmed', () => { + expect($t(` foo`)).toEqual(` foo`); + }); + test('non empty text nodes trimmed', () => { + expect($t(` `)).toEqual(null); + }); + }); + describe('MustacheStatement', () => { + test('converts a args-less path', () => { + expect($t(`{{foo-bar}}`)).toEqual(`$:foo-bar`); + }); + test('converts a path with args', () => { + expect($t(`{{foo-bar bas boo}}`)).toEqual( + `$:() => $:foo-bar(bas,boo)`, + ); + }); + test('converts sub-expression without args', () => { + expect($t(`{{(foo-bar)}}`)).toEqual( + `$:() => $:foo-bar()`, + ); + }); + test('supports helper composition', () => { + expect($t(`{{(foo-bar (baz-bat))}}`)).toEqual( + `$:() => $:foo-bar($:baz-bat())`, + ); + }); + test('support boolean literals', () => { + expect($t(`{{true}}`)).toEqual(true); + expect($t(`{{false}}`)).toEqual(false); + }); + test('support null literals', () => { + expect($t(`{{null}}`)).toEqual(null); + }); + test('support undefined literals', () => { + expect($t(`{{undefined}}`)).toEqual(undefined); + }); + test('support bool, null, undefined as helper args', () => { + expect( + $t(`{{foo true null undefined}}`), + ).toEqual(`$:() => $:foo(true,null,undefined)`); + }); + }); + describe('ElementNode', () => { + test('converts a simple element', () => { + expect($t(`
`)).toEqual( + $node({ tag: 'div' }), + ); + }); + test('converts a simple element with string attribute', () => { + expect($t(`
`)).toEqual( + $node({ + tag: 'div', + properties: [['className', 'foo']], + }), + ); + }); + test('converts a simple element with string child', () => { + expect($t(`
sd
`)).toEqual( + $node({ + tag: 'div', + events: [[EVENT_TYPE.TEXT_CONTENT, ' sd']], + }), + ); + }); + test('converts a simple element with complex child', () => { + expect($t(`
sd
`)).toEqual( + $node({ + tag: 'div', + children: [' sd', $node({ tag: 'span' })], + }), + ); + }); + test('converts a simple element with concat string attribute', () => { + expect( + $t(`
`), + ).toEqual( + $node({ + tag: 'div', + properties: [ + ['className', '$:() => [$:foo," bar ",$:boo(baks)].join(\'\')'], + ], + }), + ); + }); + test('converts a simple element with path attribute', () => { + expect($t(`
`)).toEqual( + $node({ + tag: 'div', + properties: [['className', '$:foo']], + }), + ); + }); + test('converts a simple element with path attribute with string literal', () => { + expect( + $t(`
`), + ).toEqual( + $node({ + tag: 'div', + properties: [['className', '$:() => $:foo("bar")']], + }), + ); + }); + test('converts a simple element with path attribute with path literal', () => { + expect($t(`
`)).toEqual( + $node({ + tag: 'div', + properties: [['className', '$:() => $:foo(bar)']], + }), + ); + }); + test('converts a simple element with `on` modifier', () => { + // @todo - likely need to return proper closure here (arrow function) + expect($t(`
`)).toEqual( + $node({ + tag: 'div', + events: [['click', '$:($e, $n) => $:foo($e, $n, )']], + }), + ); + }); + test('converts a simple element with `on` modifier, with composed args', () => { + // @todo - likely need to return proper closure here (arrow function) + expect( + $t(`
`), + ).toEqual( + $node({ + tag: 'div', + events: [['click', '$:($e, $n) => $:foo($:bar,$:baz)($e, $n, )']], + }), + ); + }); + test('support custom modifiers', () => { + expect($t(`
`)).toEqual( + $node({ + tag: 'div', + events: [['0', '$:($n) => $:foo-bar($n, )']], + }), + ); + }); + }); + describe('if condition', () => { + test('only true part', () => { + expect( + $t(`{{#if foo}}123{{/if}}`), + ).toEqual( + $control({ + condition: $glimmerCompat('$:foo'), + children: ['123'], + }), + ); + }); - test('both parts', () => { - expect( - $t(`{{#if foo}}123{{else}}456{{/if}}`), - ).toEqual( - $control({ - condition: $glimmerCompat('$:foo'), - children: ['123'], - inverse: ['456'], - }), - ); - }); + test('both parts', () => { + expect( + $t(`{{#if foo}}123{{else}}456{{/if}}`), + ).toEqual( + $control({ + condition: $glimmerCompat('$:foo'), + children: ['123'], + inverse: ['456'], + }), + ); + }); - test('helper in condition', () => { - expect( - $t(`{{#if (foo bar)}}123{{else}}456{{/if}}`), - ).toEqual( - $control({ - type: 'if', - condition: $glimmerCompat('$:foo($:bar)'), - children: ['123'], - inverse: ['456'], - }), - ); + test('helper in condition', () => { + expect( + $t(`{{#if (foo bar)}}123{{else}}456{{/if}}`), + ).toEqual( + $control({ + type: 'if', + condition: $glimmerCompat('$:foo($:bar)'), + children: ['123'], + inverse: ['456'], + }), + ); - expect( - $t( - `{{#unless (foo bar)}}123{{else}}456{{/unless}}`, - ), - ).toEqual( - $control({ - type: 'if', - condition: $glimmerCompat('$:foo($:bar)'), - children: ['456'], - inverse: ['123'], - }), - ); - }); - }); - describe('let condition', () => { - test('it works', () => { - expect( - $t( - `{{#let foo "name" as |bar k|}}p{{bar}}{{k}}{{/let}}`, - ), - ).toEqual( - `$:...(() => {let bar = $:() => $:foo;let k = "name";return [$_text("p"), ${ - flags.IS_GLIMMER_COMPAT_MODE ? '() => bar' : 'bar' - }, ${flags.IS_GLIMMER_COMPAT_MODE ? '() => k' : 'k'}]})()`, - ); + expect( + $t( + `{{#unless (foo bar)}}123{{else}}456{{/unless}}`, + ), + ).toEqual( + $control({ + type: 'if', + condition: $glimmerCompat('$:foo($:bar)'), + children: ['456'], + inverse: ['123'], + }), + ); + }); + }); + describe('let condition', () => { + test('it works', () => { + expect( + $t( + `{{#let foo "name" as |bar k|}}p{{bar}}{{k}}{{/let}}`, + ), + ).toEqual( + `$:...(() => {let bar = $:() => $:foo;let k = "name";return [$_text("p"), ${ + flags.IS_GLIMMER_COMPAT_MODE ? '() => bar' : 'bar' + }, ${flags.IS_GLIMMER_COMPAT_MODE ? '() => k' : 'k'}]})()`, + ); + }); + }); + describe('each condition', () => { + test('it works', () => { + expect( + $t(`{{#each foo as |bar index|}}123{{/each}}`), + ).toEqual( + $control({ + type: 'each', + condition: $glimmerCompat('$:foo'), + blockParams: ['bar', 'index'], + children: ['123'], + }), + ); + }); + test('it could provide keys', () => { + expect( + $t( + `{{#each foo key="id" as |bar index|}}123{{/each}}`, + ), + ).toEqual( + $control({ + type: 'each', + condition: $glimmerCompat('$:foo'), + blockParams: ['bar', 'index'], + children: ['123'], + key: 'id', + }), + ); + }); + }); + describe('stableChildDetection', () => { + test('detects stable child', () => { + expect($t(`
foo
`)).toEqual( + $node({ + tag: 'div', + hasStableChild: true, + events: [[EVENT_TYPE.TEXT_CONTENT, 'foo']], + }), + ); + expect($t(`

`)).toEqual( + $node({ + tag: 'div', + hasStableChild: true, + children: [$node({ tag: 'p' })], + }), + ); + expect($t(`
<:slot>
`)).toEqual( + $node({ + tag: 'div', + hasStableChild: false, + children: [$node({ tag: ':slot' })], + }), + ); + expect( + $t(`
{{#if foo}}123{{/if}}
`), + ).toEqual( + $node({ + tag: 'div', + hasStableChild: false, + children: [ + $control({ + condition: $glimmerCompat('$:foo'), + children: ['123'], + }), + ], + }), + ); + }); }); - }); - describe('each condition', () => { - test('it works', () => { - expect( - $t(`{{#each foo as |bar index|}}123{{/each}}`), - ).toEqual( - $control({ - type: 'each', - condition: $glimmerCompat('$:foo'), - blockParams: ['bar', 'index'], - children: ['123'], - }), - ); - }); - test('it could provide keys', () => { - expect( - $t( - `{{#each foo key="id" as |bar index|}}123{{/each}}`, - ), - ).toEqual( - $control({ - type: 'each', - condition: $glimmerCompat('$:foo'), - blockParams: ['bar', 'index'], - children: ['123'], - key: 'id', - }), - ); - }); - }); - describe('stableChildDetection', () => { - test('detects stable child', () => { - expect($t(`
foo
`)).toEqual( - $node({ - tag: 'div', - hasStableChild: true, - events: [[EVENT_TYPE.TEXT_CONTENT, 'foo']], - }), - ); - expect($t(`

`)).toEqual( - $node({ - tag: 'div', - hasStableChild: true, - children: [$node({ tag: 'p' })], - }), - ); - expect($t(`
<:slot>
`)).toEqual( - $node({ - tag: 'div', - hasStableChild: false, - children: [$node({ tag: ':slot' })], - }), - ); - expect($t(`
{{#if foo}}123{{/if}}
`)).toEqual( - $node({ - tag: 'div', - hasStableChild: false, - children: [ - $control({ - condition: $glimmerCompat('$:foo'), - children: ['123'], - }), - ], - }), - ); - }); - }); - describe('components', () => { - test('it forwarding props', () => { - expect($t(`
`)).toEqual( - $node({ - tag: 'DIV', - attributes: [['@name', 1]], - }), - ); + describe('components', () => { + test('it forwarding props', () => { + expect($t(`
`)).toEqual( + $node({ + tag: 'DIV', + attributes: [['@name', 1]], + }), + ); + }); }); }); }); diff --git a/plugins/converter.ts b/plugins/converter.ts index 17391c04..b1d9fe91 100644 --- a/plugins/converter.ts +++ b/plugins/converter.ts @@ -1,15 +1,17 @@ import type { ASTv1 } from '@glimmer/syntax'; import { - HBSControlExpression, - HBSNode, + type HBSControlExpression, + type HBSNode, escapeString, isPath, resolvedChildren, serializeChildren, serializePath, toObject, + setFlags, } from './utils'; import { EVENT_TYPE, SYMBOLS } from './symbols'; +import type { Flags } from './flags'; function patchNodePath(node: ASTv1.MustacheStatement | ASTv1.SubExpression) { if (node.path.type !== 'PathExpression') { @@ -51,7 +53,8 @@ function patchNodePath(node: ASTv1.MustacheStatement | ASTv1.SubExpression) { export type PrimitiveJSType = null | number | string | boolean | undefined; export type ComplexJSType = PrimitiveJSType | HBSControlExpression | HBSNode; -export function convert(seenNodes: Set) { +export function convert(seenNodes: Set, flags: Flags) { + setFlags(flags); function ToJSType(node: ASTv1.Node, wrap = true): ComplexJSType { seenNodes.add(node); if (node.type === 'ConcatStatement') { diff --git a/plugins/flags.ts b/plugins/flags.ts index a0c0d947..113a22a4 100644 --- a/plugins/flags.ts +++ b/plugins/flags.ts @@ -1,4 +1,11 @@ -export const flags = { - IS_GLIMMER_COMPAT_MODE: true, - RUN_EVENT_DESTRUCTORS_FOR_SCOPED_NODES: false, +export type Flags = { + IS_GLIMMER_COMPAT_MODE: boolean; + RUN_EVENT_DESTRUCTORS_FOR_SCOPED_NODES: boolean; }; + +export function defaultFlags() { + return { + IS_GLIMMER_COMPAT_MODE: true, + RUN_EVENT_DESTRUCTORS_FOR_SCOPED_NODES: false, + } as Flags; +} diff --git a/plugins/symbols.ts b/plugins/symbols.ts index 8e31e83f..136e2b6e 100644 --- a/plugins/symbols.ts +++ b/plugins/symbols.ts @@ -9,6 +9,7 @@ export const SYMBOLS = { ARGS: '$_args', TEXT: '$_text', COMPONENT: '$_c', + $SLOTS_SYMBOL: '$SLOTS_SYMBOL', $_GET_SLOTS: '$_GET_SLOTS', $template: '$template', $_hasBlockParams: '$_hasBlockParams', diff --git a/plugins/test.ts b/plugins/test.ts index 1b2b4cce..43231476 100644 --- a/plugins/test.ts +++ b/plugins/test.ts @@ -1,12 +1,13 @@ // https://astexplorer.net/#/gist/c2f0f7e4bf505471c94027c580af8329/c67119639ba9e8fd61a141e8e2f4cbb6f3a31de9 // https://astexplorer.net/#/gist/4e3b4c288e176bb7ce657f9dea95f052/8dcabe8144c7dc337d21e8c771413db30ca5d397 -import { preprocess, traverse, ASTv1 } from '@glimmer/syntax'; +import { preprocess, traverse, type ASTv1 } from '@glimmer/syntax'; import { type PluginItem, transformSync } from '@babel/core'; -import { HBSControlExpression, HBSNode, serializeNode } from './utils'; +import { type HBSControlExpression, type HBSNode, serializeNode } from './utils'; import { processTemplate, type ResolvedHBS } from './babel'; import { convert } from './converter'; import { SYMBOLS } from './symbols'; +import type { Flags } from './flags'; function isNodeStable(node: string | undefined) { if (typeof node === 'undefined') { @@ -57,6 +58,7 @@ export function transform( fileName: string, mode: 'development' | 'production', isLibBuild: boolean = false, + flags: Flags, ) { const programs: { meta: ResolvedHBS['flags']; @@ -85,7 +87,7 @@ export function transform( const txt = babelResult?.code ?? ''; - const { ToJSType, ElementToNode } = convert(seenNodes); + const { ToJSType, ElementToNode } = convert(seenNodes, flags); hbsToProcess.forEach((content) => { const flags = content.flags; diff --git a/plugins/utils.ts b/plugins/utils.ts index 2dd5ec13..0b48581c 100644 --- a/plugins/utils.ts +++ b/plugins/utils.ts @@ -1,6 +1,12 @@ import type { ASTv1 } from '@glimmer/syntax'; import { SYMBOLS } from './symbols'; -import { flags } from './flags'; +import type { Flags } from './flags'; + +let flags!: Flags; + +export function setFlags(f: Flags) { + flags = f; +} export type HBSControlExpression = { type: 'each' | 'if' | 'in-element' | 'yield'; @@ -370,7 +376,10 @@ export function serializeNode( args, )}, ${slotsObj}), ${secondArg}, ${ctxName}`; if (flags.IS_GLIMMER_COMPAT_MODE === false) { - fn = `${node.tag},${toObject(args)}, ${secondArg}, ${ctxName}`; + fn = `${node.tag},${toObject([ + ...args, + [`$:[${SYMBOLS.$SLOTS_SYMBOL}]`, `$:${slotsObj}`], + ])}, ${secondArg}, ${ctxName}`; } // @todo - we could pass `hasStableChild` ans hasBlock / hasBlockParams to the DOM helper // including `has-block` helper diff --git a/src/tests/integration/bare-functions-test.gts b/src/tests/integration/bare-functions-test.gts index 195e1d25..598ce698 100644 --- a/src/tests/integration/bare-functions-test.gts +++ b/src/tests/integration/bare-functions-test.gts @@ -40,6 +40,6 @@ module('Integration | bare functions rendering', function () { test('renders undefined', async function (assert) { const value = () => undefined; await render(); - assert.dom().hasText(IS_GLIMMER_COMPAT_MODE ? '' : ''); + assert.dom().hasText(''); }); }); diff --git a/src/tests/integration/bare-test.gts b/src/tests/integration/bare-test.gts index c7d1581d..8a721947 100644 --- a/src/tests/integration/bare-test.gts +++ b/src/tests/integration/bare-test.gts @@ -40,6 +40,6 @@ module('Integration | bare values rendering', function () { test('renders undefined', async function (assert) { const value = undefined; await render(); - assert.dom().hasText(IS_GLIMMER_COMPAT_MODE ? '' : 'undefined'); + assert.dom().hasText(''); }); });