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

Framework: Use a simple JS object to declare the attribute's source #2854

Merged
merged 13 commits into from Nov 16, 2017

Conversation

Projects
None yet
6 participants
@youknowriad
Contributor

youknowriad commented Oct 2, 2017

Description

This PR can be seen as the first step towards #2751

Instead of using the hpq matchers, we use a declarative API to define the attributes source

//before
import { attrfrom 'api';

attributes: {
  url: {
    type: 'string',
    source: attr( 'src' ),
  }
}

// after
attributes: {
  url: {
    type: 'string',
    source: 'attribute',
    attribute: 'src',
  }
}

Caveats

A bit verbose compared to the previous approach, noticeable when nesting

// Gallery

images: {
  type: 'array',
  source: 'query',
  selector: 'div.wp-block-gallery figure.blocks-gallery-image img',
  query: {
    source: 'object',
    object: {
      url: {
        type: 'attribute',
        attribute: 'src',
      },
      alt: {
        type: 'attribute',
        attribute: 'alt',
      },
      id: {
        type: 'attribute',
        attribute: 'data-id',
      },
    },
  },
},

Pros

  • Permits server-side attribute declaration (which opens the way to server-side attribute parsing if necessary)
  • No hpq direct dependency, no need to wrap the matchers

Checklist:

  • Refactor the parser
  • Refactor the serializer
  • Refactor the blocks
  • Fix the unit tests and adds new tests
  • Update the documentation
@westonruter

This comment has been minimized.

Show comment
Hide comment
@westonruter

westonruter Oct 2, 2017

Member

If verbosity is a concern then in JS there could be shorthand “macros” like:

const attr = ( key ) => ( {
    type: 'attribute',
    attribute: key
} );

In that way you could still do:

attributes: {
  url: {
    type: 'string',
    source: attr( 'src' ),
  }
}

If you wanted to.

Member

westonruter commented Oct 2, 2017

If verbosity is a concern then in JS there could be shorthand “macros” like:

const attr = ( key ) => ( {
    type: 'attribute',
    attribute: key
} );

In that way you could still do:

attributes: {
  url: {
    type: 'string',
    source: attr( 'src' ),
  }
}

If you wanted to.

@aduth

This comment has been minimized.

Show comment
Hide comment
@aduth

aduth Oct 2, 2017

Member

Talking with @mtias about this, it was mentioned maybe we don't need to nest source, and rather create the source-relevant properties on the same top-level, i.e.

// Before:
attributes: {
  url: {
    type: 'string',
    source: {
      type: 'attribute',
      attribute: 'src',
    }
  }
}

// After:
attributes: {
  url: {
    type: 'string',
    source: 'attribute',
    attribute: 'src',
  }
}
Member

aduth commented Oct 2, 2017

Talking with @mtias about this, it was mentioned maybe we don't need to nest source, and rather create the source-relevant properties on the same top-level, i.e.

// Before:
attributes: {
  url: {
    type: 'string',
    source: {
      type: 'attribute',
      attribute: 'src',
    }
  }
}

// After:
attributes: {
  url: {
    type: 'string',
    source: 'attribute',
    attribute: 'src',
  }
}
@youknowriad

This comment has been minimized.

Show comment
Hide comment
@youknowriad

youknowriad Oct 3, 2017

Contributor

@aduth How would you declare query( 'div', query( 'p', attr( 'className' ) ) )?

Nesting is needed IMO, and I personally prefer an explicit source property to declare the way we parse an attribute.

Contributor

youknowriad commented Oct 3, 2017

@aduth How would you declare query( 'div', query( 'p', attr( 'className' ) ) )?

Nesting is needed IMO, and I personally prefer an explicit source property to declare the way we parse an attribute.

@iseulde

This comment has been minimized.

Show comment
Hide comment
@iseulde

iseulde Oct 4, 2017

Member
source: 'attribute',
attribute: 'src',

😵

Member

iseulde commented Oct 4, 2017

source: 'attribute',
attribute: 'src',

😵

@aduth

This comment has been minimized.

Show comment
Hide comment
@aduth

aduth Oct 4, 2017

Member

I may be playing devil's advocate here, since I'd originally been a proponent of the nested source. But I'm trying to be sensitive to the impact of nesting, which I think can be harmful to general readability of the block definition and, from the perspective of the implementer, how obvious it is to structure. JSON schema is easy to pick on here, because it's... excessive ([1], [2]). While the requirements forced us to rethink the structure, I long for the simplicity we once had for defining attributes:

attributes: {
url: attr( 'img', 'src' ),
alt: attr( 'img', 'alt' ),
caption: html( 'figcaption' )
},

(Compare to current)

Of course, where we balance is on consistency and accommodating the varied needs of defining the source, where a nested object could have some merit.

@aduth How would you declare query( 'div', query( 'p', attr( 'className' ) ) )?

If we consider selector and source as the respective arguments to query:

{
	type: 'query',
	selector: 'div',
	source: {
		type: 'query',
		selector: 'p',
		source: {
			type: 'attribute',
			attribute: 'class',
		},
	},
}

Also worth considering optimizing for the more common scenario: query is the edge case, and has shown to have few use-cases thus far. Could even call into question its overall value and whether it's worth keeping around.

source: 'attribute',
attribute: 'src',

😵

You are going to have to elaborate here 😃 I assume you're not a fan of the redundancy? type may not be strictly necessary if we could infer the source type by the presence of other properties. But there is some value in being able to quickly determine (both visually for the reader, and programmatically) the type.

What would you see as the alternative here? type: 'attribute', name: 'src' ? Would it be as obvious the connection between the name property and the type of the source?

Member

aduth commented Oct 4, 2017

I may be playing devil's advocate here, since I'd originally been a proponent of the nested source. But I'm trying to be sensitive to the impact of nesting, which I think can be harmful to general readability of the block definition and, from the perspective of the implementer, how obvious it is to structure. JSON schema is easy to pick on here, because it's... excessive ([1], [2]). While the requirements forced us to rethink the structure, I long for the simplicity we once had for defining attributes:

attributes: {
url: attr( 'img', 'src' ),
alt: attr( 'img', 'alt' ),
caption: html( 'figcaption' )
},

(Compare to current)

Of course, where we balance is on consistency and accommodating the varied needs of defining the source, where a nested object could have some merit.

@aduth How would you declare query( 'div', query( 'p', attr( 'className' ) ) )?

If we consider selector and source as the respective arguments to query:

{
	type: 'query',
	selector: 'div',
	source: {
		type: 'query',
		selector: 'p',
		source: {
			type: 'attribute',
			attribute: 'class',
		},
	},
}

Also worth considering optimizing for the more common scenario: query is the edge case, and has shown to have few use-cases thus far. Could even call into question its overall value and whether it's worth keeping around.

source: 'attribute',
attribute: 'src',

😵

You are going to have to elaborate here 😃 I assume you're not a fan of the redundancy? type may not be strictly necessary if we could infer the source type by the presence of other properties. But there is some value in being able to quickly determine (both visually for the reader, and programmatically) the type.

What would you see as the alternative here? type: 'attribute', name: 'src' ? Would it be as obvious the connection between the name property and the type of the source?

@iseulde

This comment has been minimized.

Show comment
Hide comment
@iseulde

iseulde Oct 4, 2017

Member

You are going to have to elaborate here 😃

I have no critique, I just thought it was funny. :)

Member

iseulde commented Oct 4, 2017

You are going to have to elaborate here 😃

I have no critique, I just thought it was funny. :)

@youknowriad

This comment has been minimized.

Show comment
Hide comment
@youknowriad

youknowriad Oct 4, 2017

Contributor

@aduth so for the first level, the source type is stored in "source", and the nested levels it's in "type". I don't like this because it's not consistent.

Overall what you propose is just considering the first level as a superset of "source".

Contributor

youknowriad commented Oct 4, 2017

@aduth so for the first level, the source type is stored in "source", and the nested levels it's in "type". I don't like this because it's not consistent.

Overall what you propose is just considering the first level as a superset of "source".

@aduth

This comment has been minimized.

Show comment
Hide comment
@aduth

aduth Oct 9, 2017

Member

The intent would be to optimize for the common use-case, which is not query. In retrospect, I can grant that it's not great for source to be either an object or a string. To a previous point, maybe the string version of source is redundant if it can be inferred from other properties. Or maybe we need to take a harder look at the use cases for query and whether they can be solved other ways (nested blocks?) so that it can be eliminated.

Member

aduth commented Oct 9, 2017

The intent would be to optimize for the common use-case, which is not query. In retrospect, I can grant that it's not great for source to be either an object or a string. To a previous point, maybe the string version of source is redundant if it can be inferred from other properties. Or maybe we need to take a harder look at the use cases for query and whether they can be solved other ways (nested blocks?) so that it can be eliminated.

@aduth

This comment has been minimized.

Show comment
Hide comment
@aduth

aduth Oct 13, 2017

Member

Actually, the non-nested query example can be written without introducing inconsistency to how source is used:

{
	type: 'array',
	source: 'query',
	selector: 'div',
	query: {
		source: 'query',
		selector: 'p',
		query: {
			source: 'attribute',
			attribute: 'class',
		},
	},
}
Member

aduth commented Oct 13, 2017

Actually, the non-nested query example can be written without introducing inconsistency to how source is used:

{
	type: 'array',
	source: 'query',
	selector: 'div',
	query: {
		source: 'query',
		selector: 'p',
		query: {
			source: 'attribute',
			attribute: 'class',
		},
	},
}
@youknowriad

This comment has been minimized.

Show comment
Hide comment
@youknowriad

youknowriad Oct 13, 2017

Contributor

@aduth Oh! nice proposal, I'll try to update accordingly.

Contributor

youknowriad commented Oct 13, 2017

@aduth Oh! nice proposal, I'll try to update accordingly.

@aduth

This comment has been minimized.

Show comment
Hide comment
@aduth

aduth Oct 13, 2017

Member

Just now realizing I had type and source mixed up in both of my examples; didn't mean for there to be nearly as much inconsistency as there appears to be.

Member

aduth commented Oct 13, 2017

Just now realizing I had type and source mixed up in both of my examples; didn't mean for there to be nearly as much inconsistency as there appears to be.

@youknowriad

This comment has been minimized.

Show comment
Hide comment
@youknowriad

youknowriad Oct 13, 2017

Contributor

Updated per suggestion

Contributor

youknowriad commented Oct 13, 2017

Updated per suggestion

Show outdated Hide outdated blocks/library/gallery/index.js Outdated
Show outdated Hide outdated blocks/api/parser.js Outdated
Show outdated Hide outdated blocks/api/parser.js Outdated
Show outdated Hide outdated blocks/api/serializer.js Outdated
Show outdated Hide outdated blocks/api/parser.js Outdated
Show outdated Hide outdated blocks/api/registration.js Outdated
Show outdated Hide outdated editor/modes/visual-editor/block.js Outdated
@aduth

This comment has been minimized.

Show comment
Hide comment
@aduth

aduth Oct 13, 2017

Member

There are failing tests in block registration.

Member

aduth commented Oct 13, 2017

There are failing tests in block registration.

@youknowriad

This comment has been minimized.

Show comment
Hide comment
@youknowriad

youknowriad Oct 13, 2017

Contributor

There are failing tests in block registration.

Yep, I didn't take care of tests and documentation yet, wanted to leave this after we settle on the format. I can do so now.

Contributor

youknowriad commented Oct 13, 2017

There are failing tests in block registration.

Yep, I didn't take care of tests and documentation yet, wanted to leave this after we settle on the format. I can do so now.

@codecov

This comment has been minimized.

Show comment
Hide comment
@codecov

codecov bot Oct 16, 2017

Codecov Report

Merging #2854 into master will decrease coverage by 0.24%.
The diff coverage is 71.79%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2854      +/-   ##
==========================================
- Coverage   34.61%   34.36%   -0.25%     
==========================================
  Files         261      261              
  Lines        6769     7007     +238     
  Branches     1231     1317      +86     
==========================================
+ Hits         2343     2408      +65     
- Misses       3733     3837     +104     
- Partials      693      762      +69
Impacted Files Coverage Δ
blocks/library/button/index.js 9.3% <ø> (-1.81%) ⬇️
blocks/library/code/index.js 50% <ø> (-7.15%) ⬇️
blocks/library/image/index.js 50% <ø> (-2.39%) ⬇️
blocks/library/shortcode/index.js 40% <ø> (-10%) ⬇️
blocks/library/html/index.js 16.66% <ø> (-6.42%) ⬇️
blocks/library/cover-image/index.js 30.43% <ø> (-2.9%) ⬇️
blocks/library/paragraph/index.js 29.16% <ø> (-2.84%) ⬇️
blocks/library/list/index.js 6.89% <ø> (-1.06%) ⬇️
blocks/hooks/anchor.js 80% <ø> (ø) ⬆️
blocks/library/preformatted/index.js 44.44% <ø> (-5.56%) ⬇️
... and 24 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b56f09c...2aeb51a. Read the comment docs.

codecov bot commented Oct 16, 2017

Codecov Report

Merging #2854 into master will decrease coverage by 0.24%.
The diff coverage is 71.79%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2854      +/-   ##
==========================================
- Coverage   34.61%   34.36%   -0.25%     
==========================================
  Files         261      261              
  Lines        6769     7007     +238     
  Branches     1231     1317      +86     
==========================================
+ Hits         2343     2408      +65     
- Misses       3733     3837     +104     
- Partials      693      762      +69
Impacted Files Coverage Δ
blocks/library/button/index.js 9.3% <ø> (-1.81%) ⬇️
blocks/library/code/index.js 50% <ø> (-7.15%) ⬇️
blocks/library/image/index.js 50% <ø> (-2.39%) ⬇️
blocks/library/shortcode/index.js 40% <ø> (-10%) ⬇️
blocks/library/html/index.js 16.66% <ø> (-6.42%) ⬇️
blocks/library/cover-image/index.js 30.43% <ø> (-2.9%) ⬇️
blocks/library/paragraph/index.js 29.16% <ø> (-2.84%) ⬇️
blocks/library/list/index.js 6.89% <ø> (-1.06%) ⬇️
blocks/hooks/anchor.js 80% <ø> (ø) ⬆️
blocks/library/preformatted/index.js 44.44% <ø> (-5.56%) ⬇️
... and 24 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b56f09c...2aeb51a. Read the comment docs.

@youknowriad

This comment has been minimized.

Show comment
Hide comment
@youknowriad

youknowriad Oct 16, 2017

Contributor

Updated this PR with unit tests and documentation, it's in a better shape now.

Contributor

youknowriad commented Oct 16, 2017

Updated this PR with unit tests and documentation, it's in a better shape now.

@aduth

This comment has been minimized.

Show comment
Hide comment
@aduth

aduth Oct 16, 2017

Member

Hit another merge conflict 😄

Member

aduth commented Oct 16, 2017

Hit another merge conflict 😄

Show outdated Hide outdated blocks/api/parser.js Outdated
Show outdated Hide outdated blocks/api/parser.js Outdated
Show outdated Hide outdated blocks/api/registration.js Outdated
Show outdated Hide outdated blocks/api/parser.js Outdated
Show outdated Hide outdated blocks/api/parser.js Outdated
Show outdated Hide outdated blocks/library/gallery/index.js Outdated
@youknowriad

This comment has been minimized.

Show comment
Hide comment
@youknowriad

youknowriad Nov 7, 2017

Contributor

@aduth I've rebased this and addressed drop the explicit handling of "meta" attributes in the parser. Do you think this addresses the issue raised?

Contributor

youknowriad commented Nov 7, 2017

@aduth I've rebased this and addressed drop the explicit handling of "meta" attributes in the parser. Do you think this addresses the issue raised?

@aduth

This is generally looking good to me. Not considering server implications, this does add a small bit of verbosity, but I hope that working with plain objects will be easier for folks than using source functions. My previous concerns appear to be addressed.

Needs a rebase though.

Show outdated Hide outdated blocks/api/parser.js Outdated
Show outdated Hide outdated blocks/api/parser.js Outdated
Show outdated Hide outdated blocks/api/raw-handling/shortcode-converter.js Outdated
Show outdated Hide outdated blocks/api/parser.js Outdated
Show outdated Hide outdated docs/attributes.md Outdated
Show outdated Hide outdated docs/attributes.md Outdated
Show outdated Hide outdated blocks/library/freeform/index.js Outdated
Show outdated Hide outdated blocks/api/parser.js Outdated
@youknowriad

This comment has been minimized.

Show comment
Hide comment
@youknowriad

youknowriad Nov 13, 2017

Contributor

A final review for this?

Contributor

youknowriad commented Nov 13, 2017

A final review for this?

@aduth

Not sure if it's introduced by this pull request or an issue that's been resolved since last rebase, but I'm seeing many invalid blocks when simply saving Demo and refreshing the page.

Show outdated Hide outdated blocks/api/parser.js Outdated
Show outdated Hide outdated blocks/api/serializer.js Outdated

Rebased this and addressed the comments. I'm not seeing the "invalid" issue, it might have been fixed by the rebase.

Show outdated Hide outdated blocks/api/registration.js Outdated
@aduth

aduth approved these changes Nov 15, 2017

Show outdated Hide outdated blocks/api/registration.js Outdated

@youknowriad youknowriad merged commit 9cf1e9e into master Nov 16, 2017

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
continuous-integration/travis-ci/push The Travis CI build passed
Details

@youknowriad youknowriad deleted the try/declarative-attributese branch Nov 16, 2017

*/
import * as source from './source';
export { source };
export { createBlock, switchToBlockType, createReusableBlock } from './factory';
export { default as parse, getSourcedAttributes } from './parser';

This comment has been minimized.

@EphoxJames

EphoxJames Nov 21, 2017

Contributor

Bug here: getSourcedAttributes was removed from parser

@EphoxJames

EphoxJames Nov 21, 2017

Contributor

Bug here: getSourcedAttributes was removed from parser

This comment has been minimized.

@EphoxJames

EphoxJames Nov 21, 2017

Contributor

See PR with fix here:
#3582

@EphoxJames

EphoxJames Nov 21, 2017

Contributor

See PR with fix here:
#3582

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment