diff --git a/.eslintignore b/.eslintignore index a4a3d4cbd0924..7228e8b672e90 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,5 +2,6 @@ build build-module coverage node_modules +packages/block-serialization-spec-parser test/e2e/test-plugins vendor diff --git a/.eslintrc.js b/.eslintrc.js index 18ab9e6e14277..b2564dfc09afa 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -50,10 +50,13 @@ module.exports = { message: 'Use @wordpress/blob as import path instead.', }, { - selector: 'ImportDeclaration[source.value=/^blocks(\\u002F|$)/]', - message: 'Use @wordpress/blocks as import path instead.', + selector: 'ImportDeclaration[source.value=/^block-serialization-spec-parser(\\u002F|$)/]', + message: 'Use @wordpress/block-serialization-spec-parser as import path instead.', }, { + selector: 'ImportDeclaration[source.value=/^blocks(\\u002F|$)/]', + message: 'Use @wordpress/blocks as import path instead.', + },{ selector: 'ImportDeclaration[source.value=/^components(\\u002F|$)/]', message: 'Use @wordpress/components as import path instead.', }, diff --git a/bin/create-php-parser.js b/bin/create-php-parser.js index f5cd5327fd000..0d661ff0f906b 100755 --- a/bin/create-php-parser.js +++ b/bin/create-php-parser.js @@ -5,7 +5,7 @@ const phpegjs = require( 'phpegjs' ); const fs = require( 'fs' ); const path = require( 'path' ); -const peg = fs.readFileSync( 'blocks/api/post.pegjs', 'utf8' ); +const peg = fs.readFileSync( 'packages/block-serialization-spec-parser/grammar.pegjs', 'utf8' ); const parser = pegjs.generate( peg, diff --git a/bin/generate-public-grammar.js b/bin/generate-public-grammar.js index 34b5522a6eb0a..aaa3c43bc2888 100755 --- a/bin/generate-public-grammar.js +++ b/bin/generate-public-grammar.js @@ -2,7 +2,7 @@ const parser = require( '../node_modules/pegjs/lib/parser.js' ); const fs = require( 'fs' ); const path = require( 'path' ); -const grammarSource = fs.readFileSync( './blocks/api/post.pegjs', 'utf8' ); +const grammarSource = fs.readFileSync( './packages/block-serialization-spec-parser/grammar.pegjs', 'utf8' ); const grammar = parser.parse( grammarSource ); function escape( text ) { diff --git a/blocks/api/parser.js b/blocks/api/parser.js index 4aa98b09eba43..5f2ebf531f169 100644 --- a/blocks/api/parser.js +++ b/blocks/api/parser.js @@ -10,11 +10,11 @@ import { flow, castArray, mapValues, omit, stubFalse } from 'lodash'; import { autop } from '@wordpress/autop'; import { applyFilters } from '@wordpress/hooks'; import deprecated from '@wordpress/deprecated'; +import { parse as grammarParse } from '@wordpress/block-serialization-spec-parser'; /** * Internal dependencies */ -import { parse as grammarParse } from './post-parser'; import { getBlockType, getUnknownTypeHandlerName } from './registration'; import { createBlock } from './factory'; import { isValidBlock } from './validation'; @@ -365,7 +365,7 @@ export function createBlockWithFallback( blockNode ) { * * @return {Function} An implementation which parses the post content. */ -export const createParse = ( parseImplementation ) => +const createParse = ( parseImplementation ) => ( content ) => parseImplementation( content ).reduce( ( memo, blockNode ) => { const block = createBlockWithFallback( blockNode ); if ( block ) { diff --git a/blocks/api/post-parser.js b/blocks/api/post-parser.js deleted file mode 100644 index 80f5a105d53e7..0000000000000 --- a/blocks/api/post-parser.js +++ /dev/null @@ -1 +0,0 @@ -export * from './post.pegjs'; diff --git a/blocks/api/post-parser.native.js b/blocks/api/post-parser.native.js deleted file mode 100644 index 7adb6bd3ce0ad..0000000000000 --- a/blocks/api/post-parser.native.js +++ /dev/null @@ -1 +0,0 @@ -export * from './post-grammar-parser-pre-generated'; diff --git a/blocks/api/test/parser.js b/blocks/api/test/parser.js index a9b4b2ce4800c..0b811b9d9a279 100644 --- a/blocks/api/test/parser.js +++ b/blocks/api/test/parser.js @@ -16,9 +16,7 @@ import { default as parsePegjs, parseWithAttributeSchema, toBooleanAttributeMatcher, - createParse, } from '../parser'; -import { parse as grammarParsePreGenerated } from '../post-grammar-parser-pre-generated'; import { registerBlockType, unregisterBlockType, @@ -549,17 +547,11 @@ describe( 'block parser', () => { } ); } ); - describe( 'parse() of pegjs parser', () => { + describe( 'parse() of @wordpress/block-serialization-spec-parser', () => { // run the test cases using the PegJS defined parser testCases( parsePegjs ); } ); - describe( 'parse() of pre-generated parser', () => { - // run the test cases using the pre-generated parser - const parsePreGenerated = createParse( grammarParsePreGenerated ); - testCases( parsePreGenerated ); - } ); - // encapsulate the test cases so we can run them multiple time but with a different parse() function function testCases( parse ) { it( 'should parse the post content, including block attributes', () => { diff --git a/core-blocks/test/full-content.js b/core-blocks/test/full-content.js index afa71247575ad..0dc2c9005143f 100644 --- a/core-blocks/test/full-content.js +++ b/core-blocks/test/full-content.js @@ -10,12 +10,12 @@ import { format } from 'util'; * WordPress dependencies */ import { getBlockTypes, parse, serialize } from '@wordpress/blocks'; +import { parse as grammarParse } from '@wordpress/block-serialization-spec-parser'; /** * Internal dependencies */ import { registerCoreBlocks } from '../'; -import { parse as grammarParse } from '../../blocks/api/post.pegjs'; const fixturesDir = path.join( __dirname, 'fixtures' ); diff --git a/lib/client-assets.php b/lib/client-assets.php index cde5919bab1af..1fc9d11ee92e5 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -203,6 +203,13 @@ function gutenberg_register_scripts_and_styles() { filemtime( gutenberg_dir_path() . 'build/dom/index.js' ), true ); + wp_register_script( + 'wp-block-serialization-spec-parser', + gutenberg_url( 'build/block-serialization-spec-parser/index.js' ), + array(), + filemtime( gutenberg_dir_path() . 'build/block-serialization-spec-parser/index.js' ), + true + ); wp_add_inline_script( 'wp-dom', gutenberg_get_script_polyfill( array( @@ -290,7 +297,7 @@ function gutenberg_register_scripts_and_styles() { wp_register_script( 'wp-blocks', gutenberg_url( 'build/blocks/index.js' ), - array( 'wp-blob', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-shortcode', 'wp-data', 'lodash' ), + array( 'wp-blob', 'wp-deprecated', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-shortcode', 'wp-block-serialization-spec-parser', 'wp-data', 'lodash' ), filemtime( gutenberg_dir_path() . 'build/blocks/index.js' ), true ); diff --git a/package-lock.json b/package-lock.json index a65035568ca77..d604bb389330b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3029,6 +3029,9 @@ "@wordpress/blob": { "version": "file:packages/blob" }, + "@wordpress/block-serialization-spec-parser": { + "version": "file:packages/block-serialization-spec-parser" + }, "@wordpress/browserslist-config": { "version": "file:packages/browserslist-config", "dev": true @@ -15411,29 +15414,6 @@ "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=", "dev": true }, - "pegjs-loader": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/pegjs-loader/-/pegjs-loader-0.5.4.tgz", - "integrity": "sha512-ViH8WwUkc/N8H59zuarORrgCi7uxn+gDIq+Ydriw1GFJi/oUg2xvhsgDDujO6dAxRsxXMgqWESx6TKYIqHorqA==", - "dev": true, - "requires": { - "loader-utils": "^0.2.5" - }, - "dependencies": { - "loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" - } - } - } - }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", diff --git a/package.json b/package.json index 07fa9c52fa721..391fd95d665f9 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/autop": "file:packages/autop", "@wordpress/blob": "file:packages/blob", + "@wordpress/block-serialization-spec-parser": "file:packages/block-serialization-spec-parser", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", "@wordpress/core-data": "file:packages/core-data", @@ -105,7 +106,6 @@ "node-sass": "4.9.2", "path-type": "3.0.0", "pegjs": "0.10.0", - "pegjs-loader": "0.5.4", "phpegjs": "1.0.0-beta7", "postcss-color-function": "4.0.1", "postcss-loader": "2.1.3", @@ -145,7 +145,7 @@ "scripts": { "prebuild": "npm run check-engines", "clean:packages": "rimraf ./packages/*/build ./packages/*/build-module ./packages/*/build-style", - "prebuild:packages": "npm run clean:packages && cross-env INCLUDE_PACKAGES=babel-plugin-import-jsx-pragma,postcss-themes SKIP_JSX_PRAGMA_TRANSFORM=1 node ./bin/packages/build.js", + "prebuild:packages": "npm run clean:packages && lerna run build && cross-env INCLUDE_PACKAGES=babel-plugin-import-jsx-pragma,postcss-themes SKIP_JSX_PRAGMA_TRANSFORM=1 node ./bin/packages/build.js", "build:packages": "cross-env EXCLUDE_PACKAGES=babel-plugin-import-jsx-pragma,postcss-themes node ./bin/packages/build.js", "build": "npm run build:packages && cross-env NODE_ENV=production webpack", "check-engines": "check-node-version --package", diff --git a/packages/block-serialization-spec-parser/.npmrc b/packages/block-serialization-spec-parser/.npmrc new file mode 100644 index 0000000000000..43c97e719a5a8 --- /dev/null +++ b/packages/block-serialization-spec-parser/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/block-serialization-spec-parser/README.md b/packages/block-serialization-spec-parser/README.md new file mode 100644 index 0000000000000..36c93ddb6a236 --- /dev/null +++ b/packages/block-serialization-spec-parser/README.md @@ -0,0 +1,26 @@ +# @wordpress/block-serialization-spec-parser + +This library contains the grammar file (`grammar.pegjs`) for WordPress posts which is a block serialization _specification_ which is used to generate the actual _parser_ which is also bundled in this package. + +PEG parser generators are available in many languages, though different libraries may require some translation of this grammar into their syntax. For more information see: +* https://pegjs.org +* https://en.wikipedia.org/wiki/Parsing_expression_grammar + +## Installation + +Install the module + +```bash +npm install @wordpress/block-serialization-spec-parser --save +``` + +## Usage + +```js +import { parse } from '@wordpress/block-serialization-spec-parser'; + +parse( '' ); +// [{"attrs": null, "blockName": "core/more", "innerBlocks": [], "innerHTML": ""}] +``` + +

Code is Poetry.

diff --git a/blocks/api/post.pegjs b/packages/block-serialization-spec-parser/grammar.pegjs similarity index 100% rename from blocks/api/post.pegjs rename to packages/block-serialization-spec-parser/grammar.pegjs diff --git a/blocks/api/post-grammar-parser-pre-generated.js b/packages/block-serialization-spec-parser/index.js similarity index 96% rename from blocks/api/post-grammar-parser-pre-generated.js rename to packages/block-serialization-spec-parser/index.js index 44abebda2c058..95a476bc3cbdb 100644 --- a/blocks/api/post-grammar-parser-pre-generated.js +++ b/packages/block-serialization-spec-parser/index.js @@ -1,12 +1,15 @@ -/* eslint-disable */ -// TODO: enable eslint - -module.exports = /* +/* * Generated by PEG.js 0.10.0. * * http://pegjs.org/ */ -(function() { +(function(root, factory) { + if (typeof define === "function" && define.amd) { + define([], factory); + } else if (typeof module === "object" && module.exports) { + module.exports = factory(); + } +})(this, function() { "use strict"; function peg$subclass(child, parent) { @@ -237,17 +240,18 @@ module.exports = /* peg$c22 = peg$classExpectation([["a", "z"]], false, false), peg$c23 = /^[a-z0-9_\-]/, peg$c24 = peg$classExpectation([["a", "z"], ["0", "9"], "_", "-"], false, false), - peg$c25 = "{", - peg$c26 = peg$literalExpectation("{", false), - peg$c27 = "}", - peg$c28 = peg$literalExpectation("}", false), - peg$c29 = "", - peg$c30 = function(attrs) { + peg$c25 = peg$otherExpectation("JSON-encoded attributes embedded in a block's opening comment"), + peg$c26 = "{", + peg$c27 = peg$literalExpectation("{", false), + peg$c28 = "}", + peg$c29 = peg$literalExpectation("}", false), + peg$c30 = "", + peg$c31 = function(attrs) { /** **/ return maybeJSON( attrs ); }, - peg$c31 = /^[ \t\r\n]/, - peg$c32 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false), + peg$c32 = /^[ \t\r\n]/, + peg$c33 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false), peg$currPos = 0, peg$savedPos = 0, @@ -1168,15 +1172,16 @@ module.exports = /* function peg$parseBlock_Attributes() { var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12; + peg$silentFails++; s0 = peg$currPos; s1 = peg$currPos; s2 = peg$currPos; if (input.charCodeAt(peg$currPos) === 123) { - s3 = peg$c25; + s3 = peg$c26; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c26); } + if (peg$silentFails === 0) { peg$fail(peg$c27); } } if (s3 !== peg$FAILED) { s4 = []; @@ -1185,16 +1190,16 @@ module.exports = /* peg$silentFails++; s7 = peg$currPos; if (input.charCodeAt(peg$currPos) === 125) { - s8 = peg$c27; + s8 = peg$c28; peg$currPos++; } else { s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c28); } + if (peg$silentFails === 0) { peg$fail(peg$c29); } } if (s8 !== peg$FAILED) { s9 = peg$parse__(); if (s9 !== peg$FAILED) { - s10 = peg$c29; + s10 = peg$c30; if (s10 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 47) { s11 = peg$c18; @@ -1270,16 +1275,16 @@ module.exports = /* peg$silentFails++; s7 = peg$currPos; if (input.charCodeAt(peg$currPos) === 125) { - s8 = peg$c27; + s8 = peg$c28; peg$currPos++; } else { s8 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c28); } + if (peg$silentFails === 0) { peg$fail(peg$c29); } } if (s8 !== peg$FAILED) { s9 = peg$parse__(); if (s9 !== peg$FAILED) { - s10 = peg$c29; + s10 = peg$c30; if (s10 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 47) { s11 = peg$c18; @@ -1351,11 +1356,11 @@ module.exports = /* } if (s4 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 125) { - s5 = peg$c27; + s5 = peg$c28; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c28); } + if (peg$silentFails === 0) { peg$fail(peg$c29); } } if (s5 !== peg$FAILED) { s3 = [s3, s4, s5]; @@ -1379,9 +1384,14 @@ module.exports = /* } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c30(s1); + s1 = peg$c31(s1); } s0 = s1; + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c25); } + } return s0; } @@ -1390,22 +1400,22 @@ module.exports = /* var s0, s1; s0 = []; - if (peg$c31.test(input.charAt(peg$currPos))) { + if (peg$c32.test(input.charAt(peg$currPos))) { s1 = input.charAt(peg$currPos); peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c32); } + if (peg$silentFails === 0) { peg$fail(peg$c33); } } if (s1 !== peg$FAILED) { while (s1 !== peg$FAILED) { s0.push(s1); - if (peg$c31.test(input.charAt(peg$currPos))) { + if (peg$c32.test(input.charAt(peg$currPos))) { s1 = input.charAt(peg$currPos); peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c32); } + if (peg$silentFails === 0) { peg$fail(peg$c33); } } } } else { @@ -1594,4 +1604,4 @@ module.exports = /* SyntaxError: peg$SyntaxError, parse: peg$parse }; -})(); \ No newline at end of file +}); diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json new file mode 100644 index 0000000000000..eba5a9bfedc17 --- /dev/null +++ b/packages/block-serialization-spec-parser/package.json @@ -0,0 +1,30 @@ +{ + "name": "@wordpress/block-serialization-spec-parser", + "version": "1.0.0-alpha.1", + "description": "Block serialization specification parser for WordPress posts", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "block", + "spec", + "parser" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/block-serialization-spec-parser/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "devDependencies": { + "pegjs": "0.10.0" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "pegjs --format umd -o ./index.js ./grammar.pegjs" + } +} diff --git a/packages/block-serialization-spec-parser/test/index.js b/packages/block-serialization-spec-parser/test/index.js new file mode 100644 index 0000000000000..77b090a144a50 --- /dev/null +++ b/packages/block-serialization-spec-parser/test/index.js @@ -0,0 +1,23 @@ +/** + * Internal dependencies + */ +import { parse } from "../"; + +describe("block-serialization-spec-parser", () => { + test("parse() works properly", () => { + const result = parse( + "" + ); + + expect(result).toMatchInlineSnapshot(` +Array [ + Object { + "attrs": null, + "blockName": "core/more", + "innerBlocks": Array [], + "innerHTML": "", + }, +] +`); + }); +}); diff --git a/test/unit/jest.config.json b/test/unit/jest.config.json index fc2e92fc20c2a..a108d01564de5 100644 --- a/test/unit/jest.config.json +++ b/test/unit/jest.config.json @@ -21,9 +21,6 @@ "/test/unit/setup-wp-aliases.js", "/test/unit/setup-mocks.js" ], - "transform": { - "\\.pegjs$": "/test/unit/pegjs-transform.js" - }, "testPathIgnorePatterns": [ "/node_modules/", "/test/e2e" diff --git a/test/unit/pegjs-transform.js b/test/unit/pegjs-transform.js deleted file mode 100644 index b5f84d20aba00..0000000000000 --- a/test/unit/pegjs-transform.js +++ /dev/null @@ -1,16 +0,0 @@ -const pegjs = require( 'pegjs' ); - -module.exports = { - process( src ) { - // Description of PEG.js options: https://github.com/pegjs/pegjs#javascript-api - const pegOptions = { - output: 'source', - cache: false, - optimize: 'speed', - trace: false, - }; - const methodName = ( typeof pegjs.generate === 'function' ) ? 'generate' : 'buildParser'; - - return `module.exports = ${ pegjs[ methodName ]( src, pegOptions ) };`; - }, -}; diff --git a/webpack.config.js b/webpack.config.js index f1261c39911d9..793723e1f7867 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -87,6 +87,7 @@ const gutenbergPackages = [ 'a11y', 'api-fetch', 'blob', + 'block-serialization-spec-parser', 'compose', 'core-data', 'data', @@ -157,13 +158,12 @@ const config = { }, module: { rules: [ - { - test: /\.pegjs/, - use: 'pegjs-loader', - }, { test: /\.js$/, - exclude: /node_modules/, + exclude: [ + /block-serialization-spec-parser/, + /node_modules/, + ], use: 'babel-loader', }, {