ahmadawais / create-guten-block

📦 A zero-configuration #0CJS developer toolkit for building WordPress Gutenberg block plugins.
https://Awais.dev/cgb-post
MIT License
3.16k stars 326 forks source link

Separate front-end JS #73

Open danztoxsmith opened 6 years ago

danztoxsmith commented 6 years ago

This is similar to some of #37 so apologies but I thought it would be easier to create a new one for this specific request.

I've been creating a block with some JS on the front-end and to achieve this I've ejected. I think it would be nice if there was something for both front and back provided like there is for the styles, is this something you'd consider for a future version? This is the only reason I can think of for ejecting right now.

I added .editor to the original JS filenames for backend and then added new ones with the original filenames.

image

image

Awesome toolkit btw :thumbsup:

ahmadawais commented 6 years ago

@danztoxsmith thanks for that. Care to send in a PR for this. I'd be happy to accept that.

danztoxsmith commented 6 years ago

Yep, should hopefully be able to get to this later on 👍

lkhedlund commented 6 years ago

Have you had a chance to look at this @danztoxsmith @ahmadawais? Or is someone else able tackle it?

danztoxsmith commented 6 years ago

Sorry for the late response @lkhedlund. I haven't yet, I realised there is a bit more work to it than simply doing the above and due to being really busy with work lately I simply haven't been able to set aside any time to do it. Feel free to have a go, by all means. I guess you'd probably want to add a .frontend.js or something instead of changing the default one to .editor.js, people updating would encounter issues otherwise I guess.

Let me know how you get on, I may also have a go myself this weekend if I can find some time 👍

I haven't done any Gutenberg stuff for a little while but I'm going to be doing a lot of it soon so could do with this 😄

lkhedlund commented 6 years ago

@danztoxsmith I also came to the same realization, haha. I did fix it, but ended up rewriting a large chunk of the plugin to change how files were organized and managed, and didn't feel like it was appropriate for a pull request since I followed different conventions from the repos - more in line with what you might find in Sage.

sbuckpesch commented 6 years ago

+1

ahmadawais commented 6 years ago

OK we gotta move this one in. Still looking for new people to join this project and help me with this?

sbuckpesch commented 6 years ago

@ahmadawais I fixed this in my ejected and strongly modified repo. Actually it was not so complicated:

As I'm a noob on filing pull-requests (never did that for projects someone else is owning) I did not know how create one exactly. This file https://github.com/ahmadawais/create-guten-block/blob/master/CONTRIBUTING.md did not say anything about how to create the pull request... Can you write short step-by-step instructions in the Contributing.md file I can follow? Then I create a pull-request with my solution.

brianjohnhanna commented 6 years ago

@sbuckpesch Start by creating a fork, by using the Fork button in the upper right hand corner of this repository. Once you have a fork, your best bet is to create a new branch off cgb's master branch, make your proposed changes and push that branch back up to Github.

Once you've done that, head back to Github and you should see a yellow-bordered notification on the repo homepage that asks if you want to create a new pull request, which is the easiest way. The other way to get there is covered here: https://help.github.com/articles/creating-a-pull-request-from-a-fork/

ideanic commented 5 years ago

Just adding my voice to this ticket, we need this.

ahmadawais commented 5 years ago

@ideanic feel free to create a PR and I'll be happy to accept.

mattcrn commented 5 years ago

Here is a solution that works for me locally: Similar to block.js I added a frontend.js in the block and src folder.

Then, in the cgb-scripts folder, I added the frontend.js path to the path.js file and in the webpack.config files, I converted the export to an array of configs with one having the frontend.js files as entry and dist/frontend.build as output.

All that is left is to enqueue the frontend.build.js in the init.php file.

yarn start and build runs fine, haven't tried eject yet.

I made a fork and implemented the changes: https://github.com/mtthias/create-guten-block

Please note: This is not properly documented nor QA tested, I have not fully understood the codebase yet, just found a way to make this work.

Question on the side: How do I get the setup/index.js to use my modified version of cgb-scripts? It always pulls the latest one from npm.

drdogbot7 commented 5 years ago

I created a PR that attempts to fix this.

116

ahmadawais commented 5 years ago

Folks can you share what is the use case you have for a just frontend only scripts. I believe that every block should work similarly on the backend and on the frontend.

drdogbot7 commented 5 years ago

what is the use case you have for a just frontend only scripts.

Off the top of my head… Let's say I've got a simple carousel of testimonials. On the frontend I want it to just show one at a time. In the editor, that would just make it hard to work with, so I'd rather just have them all visible and stack them.

I get that we want the editor to as close to the frontend as possible, but occasionally that's going to be impractical.

drdogbot7 commented 5 years ago

A front-end only script might not be worth including by default, though I suspect this will be useful in practice. I get that it's not ideal.

More important is just getting scripts on the front-end AT ALL. The main block.build.js currently only loads in the editor… or am I just being dense about that??

Here's the files loaded in the editor image

Here's the frontend. The main block.build.js, isn't loaded because it's enqueued with enqueue_block_editor_assets. image

drdogbot7 commented 5 years ago

So, I guess if you need JS on the frontend, then the CORRECT approach would be to edit your init.php, so that your block.build.js file is enqueued in the BLOCKNAME_cgb_block_assets function, and not the BLOCKNAME_cgb_editor_assets.

I had thought that all the RegisterBlockType stuff would throw a bunch of errors outside the editor, but it doesn't seem to.

Then… to the extent that you need things to behave differently on frontend vs. editor, you can generally handle that by writing JS that doesn't suck.

Am I on the right track here?

mattcrn commented 5 years ago

In my opinion any unnecessary Javascript should be avoided in the frontend. Loading RegisterBlockType for every block when its not needed will just increase loading times. That's why I would generate a seperate frontend only js file.

I am not sure a "frontend but not editor" js file is necessary, I would leave it out for now.

ahmadawais commented 5 years ago

@mtthias when you use create-guten-block there's webpack sitting at the build process it removes any duplicate code you have and pretty sure that one reference to registering the blocks is not too much.

I have not yet investigated if that's how it is but so far it's been true. Otherwise, we'll have to create new tree shaking methods for WordPress.

mattcrn commented 5 years ago

So I probably misunderstood something, but it seemed to me like @drdogbot7 is suggesting to load all editor javascript in the frontend as well. (With the enqueue_block_assets hook) So all the custom editor logic for all my custom blocks would be loaded in the frontend where it is never used. That just doesn't seem optimal.

drdogbot7 commented 5 years ago

it seemed to me like @drdogbot7 is suggesting to load all editor javascript in the frontend as well.

That IS what I'm suggesting. It feels kinda wrong to me too, but I'm not getting any errors or any noticeable performance hit. To me it's preferable to ejecting. ¯_(ツ)_/¯

mattcrn commented 5 years ago

If @drdogbot7's suggested solution is the one that should be used, then the documentation should reflect that I guess.

In my opinion a a solution where the editor javascript is not loaded on the frontend would be preferable. (I think drdogbot7's pr without the frontend only part would be fine).

ahmadawais commented 5 years ago

@mtthias that's not the point. What I need here is clear data that makes a case for editor having enough JS bytes which are actually compiled (have cost) and have enough size to be separated! Otherwise, we can add chunks building.

mattcrn commented 5 years ago

Ok, I see, I will take a look at this.

drdogbot7 commented 5 years ago

So why does it make sense to have separate CSS for editor and front-end, but not JS? I'm tending to have much more editor-specific JS than CSS (granted uncompiled), and it seems like it would be trivial to target CSS at the editor and just enqueue one CSS file:

.edit-post-visual-editor {
  .my-block-class {
    // bla bla bla
  }
}

@ahmadawais I'm not being facetious, I'm genuinely curious! CGB and your insights have been invaluable in building my first gutenberg blocks.

mi-roh commented 5 years ago

I never worked bevor with React & Babel. Two awesome libraries nicely combined in this project. Makes it easy to dive into Gutenberg... - thx @ahmadawais

I hope I got this right. The mainproblem in this case seams to be wordpress. React is designed to build components, each component gets passed to the dom/frontend with there JS bindings & functionality. But in our case we pass the component to the editor – where we keep the HTML and lose all bindings. So it's always a dirty solution compared to the concept of React.

In the Project I'm currently working on, based on CGB, I added an interaction.js File to each Block, containing the FE-Specific JS of the block and binding the JS the classic fashion style. A src/interaction.js joins & builds these files to one file. (Interaction.js is an naming idea, it could be named bindings.js, frontend.js or something else.)

I tried to configurate a custom Loader, extracting all interaction.js-files, and building these separately – similar to the sass Files. But I don't know Babel good enough to not only cascade the interaction.js-files.

I pushed the few needed modifications to a Fork - have a look at it.

– my two cents

mattjbrent commented 5 years ago

@ahmadawais @danztoxsmith

OK, so this seems to have gotten (a lot) off-topic. @ahmadawais I have a use-case that will require the above functionality.

My current use case is that i'm creating a suite of blocks that correspond directly into components. In my basic use case, i'm adding form components, specifically a select field that requires JS to function properly.

My CGB plugin should contain all the code required for these blocks to work both in the front end and for the editor. I don't want to have to add JS to my theme to get the functionality in my block components to work. That's a violation of separation of concerns.

My solution for this would be pretty simple and if you agree, I have no problem submitting a PR.

All we need to do is create a new entry point in Webpack for front-end JS logic. This can get added into the current /src/init.php file under the plugin_name_cgb_block_assets function in an wp_enqueue_script function. To not load the file you simply remove it from the init.php file. (I wouldn't have thought it a big deal if Webpack is dealing with that one extra entry point, especially if it's just an empty file by default.

rodrigodagostino commented 5 years ago

Thank you very much, @mtthias . Your solution was exactly what I was looking for :) Hope something like this gets implemented in the main repo. I'm currently developing a slider block, and adding the whole backend JS didn't feel like a good approach.

situplastik commented 5 years ago

Hi! I had the same problem (i was doing an slider block with lightbox) and my solution was make an frontend.js file and require it (and the rest of the javascript libraries that I need) only if is not dashboard. Quite close to @mtthias proposal solution 😄

function my_block_cgb_block_assets() { // phpcs:ignore
    wp_enqueue_style(
        'my_block-cgb-style-css',
        plugins_url( 'dist/blocks.style.build.css', dirname( __FILE__ ) ),
        array( 'wp-editor' )
    );

  if(!is_admin()){ // if frontend (not wp-admin)
    wp_enqueue_script(
        'my_block-cgb-block-js-main',
        plugins_url( '/src/frontend.js', dirname( __FILE__ ) ),
        array('wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor', 'javascript-library1', 'javascript-library2' ),
        false,
        true
    );

    wp_enqueue_script('javascript-library1', 'https://example1.com/library.min.js');
    wp_enqueue_style('css-library1', 'https://example1.com/library.min.css');

    wp_enqueue_script('javascript-library2', 'https://example2.com/library.min.js');
    wp_enqueue_style('css-library2', 'https://example2.com/library.min.css');
  }
}

// Hook: Frontend assets.
add_action( 'enqueue_block_assets', 'my_block_cgb_block_assets' );
LyleBennett commented 5 years ago

@ahmadawais Firstly thanks so much for creating this, it really is such an amazing thing you're doing here.

My case for the use of frontend rendered js is required with creating an accordion for example -

export default class Accordion extends Component {

constructor( props ) {
    super( ...arguments );
    this._handleClick = this._handleClick.bind(this);
}

componentDidMount() {
    this._handleClick();
}

_handleClick() {
    const acc = this._acc.children;
    for (let i = 0; i < acc.length; i++) {
        let a = acc[i];
        a.onclick = () => a.classList.toggle("active");
    }
}

render() {

    // Setup the attributes
    const { accordionTitle, accordionText, accordionAlignment, accordionFontSize } = this.props.attributes;

    return (
        <div
            style={ {

            } }
            className={ classnames(
                this.props.className,
                accordionAlignment,
                'accordion',
                'font-size-' + accordionFontSize,
            ) }
            ref={a => this._acc = a} 
    onClick={this._handleClick}
        >
            { this.props.children }
        </div>
    );
}
}

and my save function:

save: function( props ) {

    // Setup the attributes
    const { accordionTitle, accordionText, accordionAlignment, accordionFontSize, accordionOpen } = props.attributes;

    // Save the block markup for the front end
    return (
        <div>
    <Accordion { ...props }>
      <div className="accordion" >
    <RichText.Content
    tagName="p"
    lassName="accordion-title"
    value={ accordionTitle } />
        <div className="accordion-body">
    <InnerBlocks.Content />
    </div>
      </div>
    </Accordion>
  </div>
    );
}

This works in the page editor, when clicking on the Accordion title the class "active" is toggled. But then this js is not rendered on the front end.

What would be the best way to go about getting the js to render front end?

rodrigodagostino commented 5 years ago

@situplastik if fact, that is nothing like mtthias proposal. You're simply enqueueing a JS file present inside your project. mtthias solution involves compiling the frontend.js just like it is done with the blocks.js.

rodrigodagostino commented 5 years ago

@LyleBennett you could try mtthias approach.

LyleBennett commented 5 years ago

@LyleBennett you could try mtthias approach.

This works for now thanks, but this means I'd have to create the same scripts twice for front and backend. Works but not ideal I guess when trying to keep the philosophy from this comment

I believe that every block should work similarly on the backend and on the frontend.

Or does that not make sense for some reason I'm not thinking of? It just feels redundant to me. But I guess it could also have some advantages.

rodrigodagostino commented 5 years ago

@LyleBennett I wouldn't say they should “work” similarly, since blocks obviously have different behaviors in the backend and in the frontend, but they should definitely look as similar as possible, creating a better experience for the user.

What I usually do is to focus on React for the backend and on vanilla JS for the frontend. This way you won't be repeating code, but instead will be taking advantage of the features that each side of the coin has to offer.

LyleBennett commented 5 years ago

@LyleBennett I wouldn't say they should “work” similarly, since blocks obviously have different behaviors in the backend and in the frontend, but they should definitely look as similar as possible, creating a better experience for the user.

What I usually do is to focus on React for the backend and on vanilla JS for the frontend. This way you won't be repeating code, but instead will be taking advantage of the features that each side of the coin has to offer.

Thanks for explaining, much appreciated. Makes sense.

cr0ybot commented 4 years ago

I find the inability to pass another non-editor-focused JS file through the build process very frustrating. I agree with others in this thread not wanting to include backend-focused JS on the frontend. @ahmadawais is advocating a "frontend and backend JS should be the same" approach which seems like it stems from isomorphic js, but even in that approach there are separate entry points for client and server JS. I do not want to include backend, admin-focused code in my front end. It isn't a question solely about code size/optimization, but also about a separation of concerns and, potentially, security. A simple security-related example: your editor script needs to connect to an external API with an auth token that would be leaked to the front-facing website code.

WordPress provides two separate hooks explicitly for separating your backend-focused scripts and styles from the ones that should be enqueued for both frontend and backend: enqueue_block_editor_assets vs enqueue_block_assets. And, of course, the register_block_type() args have script vs editor_script for automatically enqueuing scripts when the block is loaded, and I notice that CGB is only using editor_script. @drdogbot7 has already pointed out that there are already separate scss files for the editor and frontend—it's incongruous to state that the same is not needed in any circumstance for JS.

I'd like to also point out that the plugin may just need other scripts outside of Gutenberg blocks, but as a user we are totally locked out of the JS build pipeline unless adding to the src/blocks.js file.

I would like to at least have a blocks-frontend.js entrypoint which, yes, may share module imports that the backend also uses to display the block in the editor. This is especially useful for server-rendered blocks. Maybe you don't want to encourage the use of a frontend-only JS file, which I suppose I can respect, but in that case some way to provide more advanced settings for additional webpack entrypoints would make sense. Another option would be to just build any JS file in the root of the src folder, which would maybe fit better with this utility's "zero configuration" approach.

rodrigodagostino commented 4 years ago

I've switched to wp-scripts several months ago, which I find it way much simpler and flexible. If you're trying to have a more customized experience like we've been discussing here, this is probably the approach you're looking for.

Steytz commented 4 years ago

Noob question just started with gutenberg and wordpress in general how could i test @mtthias approach, i have a existing Block and i need frontend only JS for a Iframe etc.. so i copied the new @mtthias changes into my node_modules cgb-scripts respective files and even added the frontend.js in my main folder.. but somehow it is not working i see no frontend.js in page source nor my simple console log shows, what am i missing or doing wrong?

Edit: i got it to work i figured i dont need and should not change anything in the node_modules

so i just added this to my src/init.php and it works

wp_enqueue_script( '<% blockNamePHPLower %>-cgb-frontend-js', // Handle. plugins_url('/dist/frontend.build.js', dirname(__FILE__)), // enqueue frontend js '', false, //Verbose true // Enqueue the script in the footer. );

I wonder what about the other webpack changes in dev and production do i have to eject to change those and be able to build?