Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #27

Draft
wants to merge 62 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
aa774db
Update packages, use latest glimmer syntax
CAndRyan Feb 10, 2022
8d6656c
Replace usages of removed Glimmer types with apparent replacements
CAndRyan Feb 10, 2022
7d354f1
Update old usages of .original with casting to a PathExpression first…
CAndRyan Feb 10, 2022
46253c6
Force re-referencing of variable attributes within each loop
CAndRyan Feb 10, 2022
cb20849
Fix object namespacing broken by Glimmer update
CAndRyan Feb 10, 2022
cf34b60
Enhance nested each loop tests
CAndRyan Feb 10, 2022
17e31c5
Remove single test debug selection
CAndRyan Feb 10, 2022
d314631
Update version for local testing
CAndRyan Feb 10, 2022
d23e58e
Update with local version of glimmer, updated with partial support
CAndRyan Feb 10, 2022
01dd980
Add support for basic partial statements
CAndRyan Feb 10, 2022
a79328c
Update repo URL in package JSON
CAndRyan Feb 10, 2022
34f7583
Add yarn lock file
CAndRyan Feb 10, 2022
842d278
Add customized glimmer fork as submodule
CAndRyan Feb 10, 2022
0f3405f
Remove forked glimmer package as nested dependency or submodule
CAndRyan Feb 10, 2022
ff6d27e
Add support for importing partial templates
CAndRyan Feb 11, 2022
84d700b
Restructure partial tests describe block
CAndRyan Feb 11, 2022
3d89364
Add support for multiple partials, of same or different types
CAndRyan Feb 11, 2022
98c8c55
Add script to update local package dependency
CAndRyan Feb 11, 2022
1b5e411
Add support for partials using period to reference the provided context
CAndRyan Feb 11, 2022
cca7ac5
Add option to always include top-level context, hacky implementation …
CAndRyan Feb 12, 2022
d96d163
[WIP] Add context prop spreading
CAndRyan Feb 12, 2022
250a712
Pass context into partial by spreading path from props
CAndRyan Feb 12, 2022
898ca23
Add support for custom attributes
CAndRyan Feb 13, 2022
1bb40d2
Add custom printer to strip line breaks which feel arbitrary
CAndRyan Feb 13, 2022
8a2d094
Add initial structure to pre-pre-processing of Handlebars template to…
CAndRyan Feb 13, 2022
b8433c8
Add initial unit tests of block statement in attribute replacement
CAndRyan Feb 13, 2022
245aa16
Add test cases for block statements in attributes
CAndRyan Feb 13, 2022
db7234c
[WIP] start support for blocks in attributes
CAndRyan Feb 14, 2022
12d24c2
Update debugger launch config to support transformed files output fro…
CAndRyan Feb 14, 2022
ab3c990
Implement basic block statement in attribute translation to helper
CAndRyan Feb 14, 2022
396ba2f
Fix tests written for old method signature
CAndRyan Feb 14, 2022
52ab58b
Add support for attributes with conditional block & extra data
CAndRyan Feb 14, 2022
836369d
Refactor rewriting of block statement attributes with helpers code
CAndRyan Feb 14, 2022
b50dff2
Update block statement in attribute replacement for basic mustache st…
CAndRyan Feb 14, 2022
f90111d
Add support for multiple attributes with block statements
CAndRyan Feb 14, 2022
d8f283d
[WIP] Add hacky solution to insert generated helpers
CAndRyan Feb 14, 2022
18f283a
Replace manually generating text for helpers with generating Babel AS…
CAndRyan Feb 15, 2022
936cb1e
Consolidate literal & dependent child helper/template generation methods
CAndRyan Feb 15, 2022
ee3713c
Add support for leading attribute data
CAndRyan Feb 15, 2022
e21d1ef
Minor test update
CAndRyan Feb 15, 2022
ad4fb67
Add tests for desired helper transformations
CAndRyan Feb 15, 2022
fd09024
Add support for converting handlebars helper invocations with at leas…
CAndRyan Feb 16, 2022
65f92cf
Add support for importing helper functions
CAndRyan Feb 16, 2022
ebd6439
Uncomment test for block statement within attribute
CAndRyan Feb 16, 2022
8b274bd
Fix import of inlined helpers
CAndRyan Feb 16, 2022
49af5ba
Add support attributes conditioned on helper block statement
CAndRyan Feb 17, 2022
068b9f9
Add (commented) test for multiple attributes within the same conditio…
CAndRyan Feb 17, 2022
d67c9a2
[WIP] Rewrite attribute generating helper with custom helper for jsx
CAndRyan Feb 17, 2022
aa3e6dd
Add preprocessor to rewrite attribute generating statements (has limi…
CAndRyan Feb 18, 2022
b5b5051
Define behavior incompatible with experimental preprocessing
CAndRyan Feb 18, 2022
a08171a
Add partial support for spreading conditional attributes
CAndRyan Feb 18, 2022
266ccff
Add support for rewriting attribute generator helpers that accept cus…
CAndRyan Feb 18, 2022
1436460
Add support for custom attributes defined on helper function calls
CAndRyan Feb 18, 2022
32a07c5
Add comment for generateAttributes helper to be included with templat…
CAndRyan Feb 18, 2022
4d32be3
Fix handling of context prop when using alwaysIncludeContext without …
CAndRyan Feb 22, 2022
4bce0a5
Add test verifying partials work with only custom attributes defined
CAndRyan Feb 22, 2022
8d12e4f
Add support for with conditional statements
CAndRyan Oct 11, 2022
05ac039
Only import each helper once
CAndRyan Oct 11, 2022
bb116d6
Add tests for accessing parent context via .. operator
CAndRyan Oct 11, 2022
71dbb04
Update path preparation to handle parent context traversal (WIP)
CAndRyan Oct 12, 2022
3ebc7e9
Add support for contextualizing parameters provided to helpers in pre…
CAndRyan Oct 12, 2022
093ae8e
Update preprocessing of helpers to append helper count, for uniqueness
CAndRyan Oct 12, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# dirs
node_modules/
.vscode/
coverage/
dist/

Expand Down
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
registry = http://registry.npmjs.org
24 changes: 24 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug tests single run",
"type": "node",
"request": "launch",
"env": { "CI": "true" },
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
"args": ["--runInBand", "--no-cache", "-t should use unique name when multiple helpers exist for same attribute type$"],
"cwd": "${workspaceRoot}",
"outFiles":[
"${workspaceFolder}/dist/**/*.js",
"${workspaceFolder}/tests/**/*.js",
"${workspaceFolder}/src/**/*.js",
"!**/node_modules/**"
],
"protocol": "inspector",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"preLaunchTask": "build"
}
]
}
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
12 changes: 12 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "yarn build"
}
]
}
4 changes: 3 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export function compile(hbsCode: string, isComponent?: boolean): string
* @param [options.isComponent] Should return JSX code wrapped as a function component
* @param [options.isModule] Should return generated code exported as default
* @param [options.includeImport] Should include react import
* @param [options.alwaysIncludeContext] Should always contain a template's context reference within the top-level props
* @param [options.includeExperimentalFeatures] Should execute preprocessors to convert features not supported by the Glimmer parser (has limitations/restrictions)
* @returns JSX code
*/
export function compile(hbsCode: string, options?: { isComponent?: boolean; isModule?: boolean, includeImport?: boolean }): string
export function compile(hbsCode: string, options?: { isComponent?: boolean; isModule?: boolean, includeImport?: boolean, alwaysIncludeContext?: boolean, includeExperimentalFeatures?: boolean }): string
27 changes: 18 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"name": "handlebars-to-jsx",
"version": "0.1.5",
"version": "0.1.5-cr-2",
"author": "Danakt Frost <danakt@protonmail.com>",
"license": "MIT",
"description": "Converts Handlebars template to React component",
"main": "dist/index.js",
"scripts": {
"update": "yarn upgrade glimmer-engine",
"watch": "tsc --watch",
"test": "jest",
"build": "tsc",
Expand All @@ -20,12 +21,12 @@
],
"repository": {
"type": "git",
"url": "git+https://github.com/danakt/handlebars-to-jsx.git"
"url": "https://github.com/CAndRyan/handlebars-to-jsx.git"
},
"bugs": {
"url": "https://github.com/danakt/handlebars-to-jsx/issues"
"url": "https://github.com/CAndRyan/handlebars-to-jsx.git/issues"
},
"homepage": "https://github.com/danakt/handlebars-to-jsx",
"homepage": "https://github.com/CAndRyan/handlebars-to-jsx.git",
"typings": "index.d.ts",
"devDependencies": {
"@types/babel__generator": "^7.0.2",
Expand All @@ -34,7 +35,7 @@
"@types/node": "^12.6.8",
"@types/object-hash": "^1.3.0",
"@types/prettier": "^1.18.0",
"eslint": "^8.6.0",
"eslint": "7.32.0",
"eslint-config-prettier": "^6.0.0",
"eslint-config-standard": "^13.0.1",
"eslint-plugin-arca": "^0.8.1",
Expand All @@ -50,14 +51,14 @@
"prettier": "^1.18.2",
"ts-jest": "^26.1.0",
"ts-node": "^8.3.0",
"typescript": "^3.5.3",
"typescript": "^4.0.2",
"typescript-eslint-parser": "^22.0.0"
},
"dependencies": {
"@babel/generator": "^7.5.5",
"@babel/parser": "^7.5.5",
"@babel/types": "^7.5.5",
"@glimmer/syntax": "^0.38.4",
"glimmer-engine": "file:../forked-glimmer-vm",
"is-self-closing": "^1.0.1",
"react-attr-converter": "^0.3.1",
"reserved-words": "^0.1.2"
Expand All @@ -74,7 +75,7 @@
},
"globals": {
"ts-jest": {
"tsConfig": "tsconfig.json"
"tsconfig": "tsconfig.json"
}
},
"testMatch": [
Expand All @@ -84,11 +85,19 @@
"collectCoverageFrom": [
"lib/**",
"ui/**"
],
"coveragePathIgnorePatterns": [
"/node_modules/",
"/_dependencies/"
],
"roots": [
"<rootDir>/src/",
"<rootDir>/tests/"
]
},
"husky": {
"hooks": {
"pre-push": "npm run build && npm test"
"pre-push": ""
}
}
}
16 changes: 11 additions & 5 deletions src/blockStatements.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { AST as Glimmer } from '@glimmer/syntax'
import { AST as Glimmer } from 'glimmer-engine/dist/@glimmer/syntax'
import * as Babel from '@babel/types'
import { resolveExpression, createRootChildren, createPath, appendToPath } from './expressions'
import { createFragment } from './elements'
import { DEFAULT_NAMESPACE_NAME, DEFAULT_KEY_NAME } from './constants'
import { DEFAULT_EACH_LOOP_NAMESPACE, DEFAULT_KEY_NAME } from './constants'

/**
* Resolves block type
*/
export const resolveBlockStatement = (blockStatement: Glimmer.BlockStatement) => {
switch (blockStatement.path.original) {
switch ((blockStatement.path as Glimmer.PathExpression).original) {
case 'if': {
return createConditionStatement(blockStatement, false)
}
Expand All @@ -21,8 +21,14 @@ export const resolveBlockStatement = (blockStatement: Glimmer.BlockStatement) =>
return createEachStatement(blockStatement)
}

// TODO: consider an alternative approach to handling 'with' statements...
// treat the inner contents as a separate template, maintaining the flatter property retrieval
case 'with': { // TODO: does this handle with-else by default?
return createConditionStatement(blockStatement, false)
}

default: {
throw new Error(`Unexpected ${blockStatement.path.original} statement`)
throw new Error(`Unexpected ${(blockStatement.path as Glimmer.PathExpression).original} statement`)
}
}
}
Expand Down Expand Up @@ -80,7 +86,7 @@ export const createEachStatement = (blockStatement: Glimmer.BlockStatement) => {
)

const mapCallback = Babel.arrowFunctionExpression(
[Babel.identifier(DEFAULT_NAMESPACE_NAME), Babel.identifier(DEFAULT_KEY_NAME)],
[Babel.identifier(DEFAULT_EACH_LOOP_NAMESPACE), Babel.identifier(DEFAULT_KEY_NAME)],
wrappedCallbackChildren
)

Expand Down
2 changes: 1 addition & 1 deletion src/comments.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AST as Glimmer } from '@glimmer/syntax'
import { AST as Glimmer } from 'glimmer-engine/dist/@glimmer/syntax'
import * as Babel from '@babel/types'

export const createComment = (
Expand Down
3 changes: 2 additions & 1 deletion src/componentCreator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as Babel from '@babel/types'
import { DEFAULT_GLOBAL_NAMESPACE } from './constants';

/**
* Creates arrow component
*/
export const createComponent = (body: Babel.Expression) =>
Babel.arrowFunctionExpression([Babel.identifier('props')], body)
Babel.arrowFunctionExpression([Babel.identifier(DEFAULT_GLOBAL_NAMESPACE)], body)
9 changes: 8 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
/** Default props name */
export const DEFAULT_GLOBAL_NAMESPACE = 'props'

/** Default namespace name */
export const DEFAULT_NAMESPACE_NAME = 'item'
export const DEFAULT_EACH_LOOP_NAMESPACE = 'item'

/** @todo Describe me */
export const DEFAULT_KEY_NAME = 'i'

export const DEFAULT_PARTIAL_NAMESPACE = 'context'

export const ATTRIBUTE_GENERATOR_HELPER_FUNCTION = 'generateAttributes';
120 changes: 110 additions & 10 deletions src/elements.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { AST as Glimmer } from '@glimmer/syntax'
import * as Babel from '@babel/types'
import * as isSelfClosing from 'is-self-closing'
import * as convertHTMLAttribute from 'react-attr-converter'
import { createConcat, resolveExpression, createChildren } from './expressions'
import { createStyleObject } from './styles'
import { AST as Glimmer } from 'glimmer-engine/dist/@glimmer/syntax'
import * as Babel from '@babel/types'
import * as isSelfClosing from 'is-self-closing'
import * as convertHTMLAttribute from 'react-attr-converter'
import { createConcat, resolveExpression, createChildren, resolveStatement } from './expressions'
import { createStyleObject } from './styles'
import { getProgramOptions } from './programContext'
import { DEFAULT_PARTIAL_NAMESPACE, ATTRIBUTE_GENERATOR_HELPER_FUNCTION } from './constants'

/**
* Creates JSX fragment
Expand All @@ -26,10 +28,18 @@ export const createFragment = (
/**
* Coverts AttrNode to JSXAttribute
*/
export const createAttribute = (attrNode: Glimmer.AttrNode): Babel.JSXAttribute | null => {
// Unsupported attribute
const reactAttrName = convertHTMLAttribute(attrNode.name)
export const createAttribute = (attrNode: Glimmer.AttrNode): Babel.JSXAttribute | Babel.JSXSpreadAttribute | null => {
const { includeExperimentalFeatures } = getProgramOptions();
if (includeExperimentalFeatures && attrNode.name === ATTRIBUTE_GENERATOR_HELPER_FUNCTION) {
const customHelperIdentifier = Babel.identifier(ATTRIBUTE_GENERATOR_HELPER_FUNCTION);
const innerHelperStatement = (attrNode.value as Glimmer.ConcatStatement).parts[0];
const innerExpression = resolveStatement(innerHelperStatement);
const customHelperCallExpression = Babel.callExpression(customHelperIdentifier, [innerExpression]);

return Babel.jsxSpreadAttribute(customHelperCallExpression);
}

const reactAttrName = convertHTMLAttribute(attrNode.name)
if (!/^[_\-A-z0-9]+$/.test(reactAttrName)) {
return null
}
Expand All @@ -48,7 +58,7 @@ export const createAttribute = (attrNode: Glimmer.AttrNode): Babel.JSXAttribute
}

case 'MustacheStatement': {
return Babel.jsxAttribute(name, Babel.jsxExpressionContainer(resolveExpression(value.path)))
return Babel.jsxAttribute(name, Babel.jsxExpressionContainer(resolveStatement(value)))
}

case 'ConcatStatement': {
Expand Down Expand Up @@ -83,3 +93,93 @@ export const convertElement = (node: Glimmer.ElementNode): Babel.JSXElement => {
isElementSelfClosing
)
}

const createObjectProperty = (hashPair: Glimmer.HashPair): Babel.ObjectProperty => {
const propertyNameIdentifier = Babel.identifier(hashPair.key);
const innerValueExpression = resolveExpression(hashPair.value);

return Babel.objectProperty(propertyNameIdentifier, innerValueExpression);
};

const createJsxAttribute = (hashPair: Glimmer.HashPair): Babel.JSXAttribute => {
const nameIdentifier = Babel.jsxIdentifier(hashPair.key);
const innerValueExpression = resolveExpression(hashPair.value);
const valueExpressionContainer = Babel.jsxExpressionContainer(innerValueExpression);

return Babel.jsxAttribute(nameIdentifier, valueExpressionContainer);
};

const createPropsSpreadContextAttribute = (paramExpression: Glimmer.Expression | null, customAttributes: Glimmer.HashPair[]): Babel.JSXAttribute => {
const customProperties: (Babel.SpreadElement | Babel.ObjectProperty)[] = customAttributes.map(createObjectProperty);

if (paramExpression !== null) {
const innerValueExpression = resolveExpression(paramExpression);
const propsSpreadElement = Babel.spreadElement(innerValueExpression);
customProperties.unshift(propsSpreadElement);
}

const valueExpression = Babel.objectExpression(customProperties);
const valueExpressionContainer = Babel.jsxExpressionContainer(valueExpression);
const nameIdentifier = Babel.jsxIdentifier(DEFAULT_PARTIAL_NAMESPACE);

return Babel.jsxAttribute(nameIdentifier, valueExpressionContainer);
};

const createPropsContextAttribute = (paramExpression: Glimmer.Expression | null, customAttributes: Glimmer.HashPair[]): Babel.JSXAttribute | null => {
if (paramExpression === null && customAttributes.length === 0) {
return null;
}

if (customAttributes.length > 0) {
return createPropsSpreadContextAttribute(paramExpression, customAttributes);
}
else if (paramExpression === null) {
return null;
}

const innerValueExpression = resolveExpression(paramExpression);
const valueExpressionContainer = Babel.jsxExpressionContainer(innerValueExpression);
const nameIdentifier = Babel.jsxIdentifier(DEFAULT_PARTIAL_NAMESPACE);

return Babel.jsxAttribute(nameIdentifier, valueExpressionContainer);
};

const createPropsSpreadAttribute = (paramExpression: Glimmer.Expression | null): Babel.JSXSpreadAttribute | null => {
if (paramExpression === null) {
return null;
}

const innerValueExpression = resolveExpression(paramExpression)

return Babel.jsxSpreadAttribute(innerValueExpression);
};

const getAttributes = (partialStatement: Glimmer.PartialStatement): (Babel.JSXAttribute | Babel.JSXSpreadAttribute)[] => {
const { includeContext } = getProgramOptions();
const contextParameter = partialStatement.params.length === 0 ? null : partialStatement.params[0];
const propsSpreadAttribute = includeContext ? createPropsContextAttribute(contextParameter, partialStatement.hash.pairs) : createPropsSpreadAttribute(contextParameter);
if (propsSpreadAttribute === null) {
return [];
}

const customAttributes = includeContext ? [] : partialStatement.hash.pairs.map(createJsxAttribute);

return [propsSpreadAttribute, ...customAttributes];
}

/**
* Converts a partial statement from Handlebars to a JSXElement
*/
export const convertPartialStatement = (partialStatement: Glimmer.PartialStatement): Babel.JSXElement => {
const jsxElementName = (partialStatement.name as Glimmer.PathExpression).original;
const tagName = Babel.jsxIdentifier(jsxElementName)
const attributes = getAttributes(partialStatement);
const isElementSelfClosing = true;

return Babel.jsxElement(
Babel.jsxOpeningElement(tagName, attributes, isElementSelfClosing),
Babel.jsxClosingElement(tagName),
[],
isElementSelfClosing
)
}
Loading