-
Notifications
You must be signed in to change notification settings - Fork 683
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
[PWA-334] Implement PageBuilder as an extension #2137
Conversation
|
feat(extensibility): pagebuilder is a separate module test: coverage for new pagebuilder test: BuildBus coverage
e21a4b8
to
636d947
Compare
Our team started testing PageBuilder in PWA last week, and I'm collating some questions we had after seeing these changes. Question OnePageBuilder components seemed to be re-redefined in three places, and now four potentially.
Now (3) is split into:
We DO NOT have a significant amount of content to migrate, and could ignore PageBuilder (or CMS entirely by deploying content as code) as a shortcut. What we don't want to do is sink time into modifying PageBuilder components in PWA or PHP then see a consolidation of these redefinitions. As of this PR, we're not sure if we should freeze our work customizing in the Question TwoIn the PageBuilder So it looks like two paradigms are implied "application components" + "CMS components" and they don't share. Is this an intentional split for separate concerns? Again we don't really want to customize heavily if there's a long-term consolidation planned, but we don't expect to be able to use |
|
||
module.exports = api => { | ||
const richContentRenderers = api.getTarget('richContentRenderers'); | ||
api.getTarget('@magento/pwa-buildpack', 'webpackCompiler').tap( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine for other parts of the code that declare their own targets in the future this implementation of handling the target will look very similar and won't change that much. Any chance this can be generalised so it can be more easily reused?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's more than a chance; there's a plan! We plan to leave these code snippets in place for a while, as we implement more and more functionality as extensions. As patterns appear, we will abstract them into a library of extension utility functions. This way, we can grow towards a more terse, declarative model, while still supporting lower-level customizations.
Astute observation. Venia and PageBuilder components are separate because they're two existing, independent systems that work differently. They're not separate concerns—both are responsible for rendering interactive, responsive UI—but they differ on a few core implementation principles.
It would be hard to consolidate these components without reconciling these differences and others. For example, PageBuilder's browser support matrix includes Internet Explorer versions that Venia's does not. This doesn't mean consolidating our components is impossible, just that it would amount to a refactor of PageBuilder's rendering code (major version, regression, etc.), which is not something we're undertaking at the moment. That said, there's opportunity for it to happen. We'll be expanding Venia's usage of custom properties soon, so that style values (e.g., colors) will properly derive from design tokens. We're doing that to offer an easier way to "theme" Venia, but it'll also do a better job of exposing our design architecture to PageBuilder and other extensions—and they may opt to make use of those properties. |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome. 166 files touched would seem like a lot, but it's almost entirely renames and fixing imports. I've left a few small questions in places, but this is very cool.
test('renders a RichContent component', () => { | ||
const component = createTestInstance(<RichContent />); | ||
|
||
expect(component.toJSON()).toMatchSnapshot(); | ||
}); | ||
|
||
test('renders a RichContent component with Page Builder content', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these tests obsolete?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah
packages/venia-concept/package.json
Outdated
@@ -12,7 +12,7 @@ | |||
"scripts": { | |||
"build": "yarn run build:prod", | |||
"build:analyze": "yarn run clean && mkdir dist && webpack -p --profile --no-progress --env.mode production --json > dist/build-stats.json && webpack-bundle-analyzer dist/build-stats.json", | |||
"build:dev": "yarn run clean && yarn run validate-queries && webpack --progress --env.mode development", | |||
"build:dev": "yarn run clean && webpack --no-progress --env.mode development", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reasoning behind this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a reason, but it's not relevant to this PR, so I'm reverting.
@@ -49,6 +49,7 @@ | |||
"@babel/runtime": "~7.4.2", | |||
"@magento/babel-preset-peregrine": "~1.0.1", | |||
"@magento/eslint-config": "~1.5.0", | |||
"@magento/pagebuilder": "~1.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
@@ -88,6 +89,9 @@ | |||
"graphql-tag": "~2.10.1", | |||
"html-webpack-plugin": "~3.2.0", | |||
"informed": "~2.1.13", | |||
"jarallax": "~1.11.1", | |||
"load-google-maps-api": "~2.0.1", | |||
"lodash.escape": "~4.0.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have to add PB's dependencies here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm inclined to declare as many dependencies as possible as peerDependencies
, which is what they are in @magento/pagebuilder
. That's a safer model, to me, for building an efficient app. It enforces that only one copy of jarallax
and other such libraries can be in the same bundle at one time.
When a dev installs @magento/pagebuilder
, they'll see warnings that they also need to add these packages as dependencies. So then they can, and they'll have control over those package versions. If another package wants an incompatible version of jarallax
, it'll fail the build, rather than silently putting two copies of the same library into the bundle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see why. If these are peer dependenies of @magento/pagebuilder
, that does explain why they would be listed as dependencies of @magento/venia-concept
. Wouldn't they be real dependencies here, though, rather than dev dependencies?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zetlen if the application did not have @magento/pagebuilder
installed, would the bundle still include Jarallax etc? Or would these only be included if the bundler discovered their usage in the application?
I'm inclined to say, any package that is optional / installable should own it's dependencies and they should be automatically installed alongside the requirement of the package. If you didn't have @magento/pagebuilder
installed seeing these in the @magento/venia-concept
package is a little strange.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@davemacaulay That is an interesting question. In this peerDeps driven workflow, here's what the workflow would be:
- Without PageBuilder installed, none of those other deps are installed either
yarn add @magento/pagebuilder
- Install process shows warnings about installing
jarallax
and other dependencies - Build fails if those dependencies are not installed
- Developer must install those deps as well
This happens with Babel and other common tools, but I would understand if it feels too complex for a new NPM/yarn user. There may be other good ways to prevent multiple copies of a library in the same bundle; do you have an alternative suggestion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zetlen I guess I'm used to the world of composer where it'll automatically resolve and install all dependencies on install, but you're right in saying if they required different versions things would get a little messy.
I don't have an alternative and if this is the way to go it makes enough sense to me. I'd be inclined to say we shouldn't include the deps for Page Builder in venia-concept
, as any other package not authored by us won't have this luxury, but as we're going to include @magento/pagebuilder
by default for now we probably have too.
If you have any suggestions of how we can avoid this that'd be great, but not necessary for this iteration.
@@ -0,0 +1,13 @@ | |||
class BuildBusPlugin { | |||
constructor(bus) { | |||
this.bus = bus; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No default value necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, nobody's creating these but us.
<div className={classes.root}> | ||
<PageBuilder masterFormat={html} /> | ||
</div> | ||
const newProps = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider avoiding new
in vars. 👍
const newProps = { | |
const nextProps = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Renamed to be more specific.
}, | ||
"homepage": "https://github.com/magento-research/pwa-studio/tree/master/packages/pagebuilder#readme", | ||
"dependencies": {}, | ||
"devDependencies": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems inaccurate to me. I would expect most of these to be actual dependencies, not dev dependencies. No?
Related, are you using NPM instead of Yarn?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the core repo I'm using Yarn, but this system works with either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, all of the dependencies that PageBuilder needs at runtime are listed as peerDependencies
, not literal dependencies. See my other comment for an explanation of that.
It enforces that only one copy of jarallax and other such libraries can be in the same bundle at one time.
The NPM/Yarn model is built totally around the idea of tolerating mutually incompatible versions of a library. That's great for Node, and browser apps without strict performance budgets, but we need to be more careful--and peer deps are the NPM-supported way of doing that.
richContentRenderers.add('PageBuilder', '@magento/pagebuilder'); | ||
} | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really concise.
@@ -26,7 +26,7 @@ | |||
} | |||
|
|||
.galleryItems { | |||
composes: items from '../../../../Gallery/gallery.css'; | |||
composes: items from '~@magento/venia-ui/lib/components/Gallery/gallery.css'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accidental tilde?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accidental tilde?
Not accidental. Required for absolute node_modules
refs since [email protected]
.
@zetlen I love the new API, the method calls and reasoning behind the changes make a lot of sense, I'm happy to move ahead with making those alterations if everyone else is. |
|
@fooman Yes, that's the idea. There exist "backdoors" for packages to inspect each others' extension points, but this API has a few safeguards for that:
With this architecture we can add rules in the future pretty easily, such as preventing any package from running |
@zetlen Ran through verification steps looks good. Stage server looking good. So pending issue is about env variables, ex - GOOGLE_MAPS_API_KEY. Also one more minor thing @dhaecker Would you like to take a quick look? |
To support extensible environment variables, I need to add overloads to some public methods. My changeset has gotten large; we can either merge a lot of complex code or make this another major release, so I can introduce breaking changes to Buildpack APIs. @dpatil-magento we can talk about this internally. |
Created bug https://jira.corp.magento.com/browse/PWA-379 for env variable issue. Its good to merge once @dhaecker approves. |
@dpatil-magento have you tested anything page builder related in this PR (what have you tested)? Thank you |
@dhaecker I had deployed these changes to https://pr-2168.pwa-venia.com/ and compared home page with https://pb.pwa-venia.com/ on Chrome. |
@dpatil-magento After speaking with Dave M. about the changes, no functionality is expected to change in Page Builder content so your testing is good enough. I approve ✅ |
Description
PageBuilder has moved into its own package
@magento/pagebuilder
. It now integrates into Venia using a brand new plugin pipeline!Dependencies
The
@magento/venia-ui
package does not depend on@magento/pagebuilder
. All direct references to@magento/pagebuilder
in that package have been removed. Instead,@magento/venia-ui
exposes a new extension point and PageBuilder integrates into it.The new
@magento/pagebuilder
package depends on@magento/venia-ui
as a peerDependency. It contains a few direct imports of@magento/venia-ui
components in its code. We want PageBuilder to be independent of Venia in the future, but we need to refactor its code to be more independent first.The
@magento/venia-concept
package depends on@magento/pagebuilder
and@magento/venia-ui
. Venia "installs"@magento/pagebuilder
just by putting it in its dependencies list!This satisfies the use case where a developer should be able to say
npm install @magento/pagebuilder
and it will automatically integrate into their UI on the next build. Magento!Extension Model
The extension model works entirely at build time, making it superior to other extension systems in performance and bundle size. It's powered by a new concept called the BuildBus. The BuildBus is an object much like an event bus, but it uses the interceptor patterns from Tapable to enable modules to declare, and share, extension points. This is good because it's the same as the popular, successful system in the build tool we already use: Webpack compiler and compilation hooks. (In our project we will call these objects "targets", to avoid confusion with the substantially different concept of React hooks.)
Modules which are extensions can integrate with BuildBus by putting a custom property
pwa-studio
in theirpackage.json
files:The above configuration tells BuildBus that this package both declares its own targets and intercepts other targets. Specifically:
@magento/venia-ui/targets/declare
. There, Venia UI exposes its own targets for other extensions to modify it.@magento/venia-ui/targets/intercept
. There, Venia UI injects itself into other modules' targets, and invokes its own targets to enable other modules to interact with its build.BuildBus does the following:
@magento/pagebuilder
runs after@magento/venia-ui
BuildBusStop API
Declare files and intercept files have access to a "BuildBusStop" facade for the BuildBus, so that BuildBus can track their requests and declarations. The facade has two methods:
api.declareTarget(targetName, target)
: Declare a named target that other modules can use.targetName
must be a string, which will be the public name of this extension point.target
must be an instance of aTapable
class.api.getTarget(moduleName, targetName)
: Retrieve a target from your own declared targets or another module. Callingapi.getTarget()
with one argument will try to retrieve the current module's named target. Callingapi.getTarget()
with two arguments will try to retrieve the targets from a different module.Once the targets are retrieved, they can be tapped in many ways; see the Tapable docs for the complete API.
Declaring and Intercepting Targets
Modules which declare targets can choose how to invoke those targets. This should usually happen in the
intercept
phase: for a module's target to do anything, it must in turn intercept an upstream target in one of its dependencies, and in its own handler it can invoke its named target with any arguments it chooses.Current Extension Points
(The list will get a lot longer; this PR only implements enough extension points to do a PageBuilder integration.)
BuildBus
BuildBus has some builtin targets; these targets hook directly in to the build process, and all extension targets are ultimately powered by interceptors on these base targets.
specialFeatures
: Runs before the compilation begins. Interceptors will receive thespecial
configuration object that the root project argued toconfigureWebpack()
. They can add to that object, indicating that the current package containsesModules
,cssModules
,rootComponents
, et cetera.webpackCompiler
: Runs when a Webpack compiler is created. Interceptors will receive the compiler object, which has ahook
property containing Tapable objects with the exact same API as BuildBus targets. Interceptors ofwebpackCompiler
will usually tap into the compiler's hooks, which allows them to interact with the build.Venia UI
Venia UI declares a
richContentRenderers
target. Interceptors to this target receive an API object which allows them to add additional content renderers to theRichContent
component. Each content renderer must be a module that exposes acanRender
function, which receives CMS content data and should return true if the renderer can display it, and aComponent
React component, which will be used to render. These renderers are added to a list in dependency order, where the first one which can render will be used. It builds that list manually by editing source code at build time, injecting direct references to all rich content renderers.Related Issue
Closes [PWA-334]..
Acceptance
Verification Stakeholders
@davemacaulay
@jimbo
@sirugh
@supernova-at
@revanth0212
@jcalcaben
@tjwiebell
@awilcoxa
Specification
PageBuilder functionality must be reviewed.
Fallback HTML render must be reviewed.
Build times and bundle sizes must be analyzed and compared.
Verification Steps
MAGENTO_BACKEND_URL
to https://dave-mhvaqay-vzsrtettsztvg.us-4.magentosite.cloud/ to access an instance with PageBuilder data on the home page.@magento/pagebuilder
frompackages/venia-concept/package.json
.yarn watch:venia
.@magento/pagebuilder
back to the dependencies list inpackages/venia-concept/package.json
.yarn watch:venia
.Checklist