diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6c10a4eaf9b..6750bd8eb20 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -118,7 +118,7 @@ jobs: - name: Install dependencies if: ${{ steps.find-files.outputs.files }} - run: composer create-project symfony-tools/code-block-checker _checker + run: composer create-project symfony-tools/code-block-checker:@dev _checker - name: Install test application if: ${{ steps.find-files.outputs.files }} diff --git a/_build/redirection_map b/_build/redirection_map index 1acae2a1667..e7146d2fc2d 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -513,6 +513,7 @@ /components/stopwatch https://github.com/symfony/stopwatch /service_container/3.3-di-changes https://symfony.com/doc/3.4/service_container/3.3-di-changes.html /frontend/encore/shared-entry /frontend/encore/split-chunks +/frontend/encore/page-specific-assets /frontend/encore/simple-example#page-specific-javascript-or-css /testing/functional_tests_assertions /testing#testing-application-assertions /components https://symfony.com/components /components/index https://symfony.com/components diff --git a/frontend.rst b/frontend.rst index 30202523b41..4272cb8338d 100644 --- a/frontend.rst +++ b/frontend.rst @@ -40,7 +40,7 @@ Getting Started ............... * :doc:`Installation ` -* :doc:`First Example ` +* :doc:`Using Webpack Encore ` Adding more Features .................... @@ -67,7 +67,6 @@ Guides ...... * :doc:`Using Bootstrap CSS & JS ` -* :doc:`Creating Page-Specific CSS/JS ` * :doc:`jQuery and Legacy Applications ` * :doc:`Passing Information from Twig to JavaScript ` * :doc:`webpack-dev-server and Hot Module Replacement (HMR) ` diff --git a/frontend/encore/installation.rst b/frontend/encore/installation.rst index c53dddd5d3f..eb4b82e8b45 100644 --- a/frontend/encore/installation.rst +++ b/frontend/encore/installation.rst @@ -82,15 +82,13 @@ is the main config file for both Webpack and Webpack Encore: /* * ENTRY CONFIG * - * Add 1 entry for each "page" of your app - * (including one that's included on every page - e.g. "app") - * * Each entry will result in one JavaScript file (e.g. app.js) * and one CSS file (e.g. app.css) if your JavaScript imports CSS. */ .addEntry('app', './assets/app.js') - //.addEntry('page1', './assets/page1.js') - //.addEntry('page2', './assets/page2.js') + + // enables the Symfony UX Stimulus bridge (used in assets/bootstrap.js) + .enableStimulusBridge('./assets/controllers.json') // When enabled, Webpack "splits" your files into smaller pieces for greater optimization. .splitEntryChunks() @@ -112,6 +110,10 @@ is the main config file for both Webpack and Webpack Encore: // enables hashed filenames (e.g. app.abc123.css) .enableVersioning(Encore.isProduction()) + .configureBabel((config) => { + config.plugins.push('@babel/plugin-proposal-class-properties'); + }) + // enables @babel/preset-env polyfills .configureBabelPresetEnv((config) => { config.useBuiltIns = 'usage'; @@ -124,16 +126,15 @@ is the main config file for both Webpack and Webpack Encore: // uncomment if you use TypeScript //.enableTypeScriptLoader() + // uncomment if you use React + //.enableReactPreset() + // uncomment to get integrity="..." attributes on your script & link tags // requires WebpackEncoreBundle 1.4 or higher //.enableIntegrityHashes(Encore.isProduction()) // uncomment if you're having problems with a jQuery plugin //.autoProvidejQuery() - - // uncomment if you use API Platform Admin (composer require api-admin) - //.enableReactPreset() - //.addEntry('admin', './assets/admin.js') ; module.exports = Encore.getWebpackConfig(); @@ -154,10 +155,8 @@ Next, open the new ``assets/app.js`` file which contains some JavaScript code // any CSS you import will output into a single css file (app.css in this case) import './styles/app.css'; - // Need jQuery? Install it with "yarn add jquery"(or "npm install jquery"), then uncomment to import it. - // import $ from 'jquery'; - - console.log('Hello Webpack Encore! Edit me in assets/app.js'); + // start the Stimulus application + import './bootstrap'; And the new ``assets/styles/app.css`` file: @@ -168,7 +167,37 @@ And the new ``assets/styles/app.css`` file: background-color: lightgray; } +You should also add an ``assets/bootstrap.js`` file, which initializes Stimulus: +a system that you'll learn about soon: + +.. code-block:: javascript + + // assets/bootstrap.js + import { startStimulusApp } from '@symfony/stimulus-bridge'; + + // Registers Stimulus controllers from controllers.json and in the controllers/ directory + export const app = startStimulusApp(require.context( + '@symfony/stimulus-bridge/lazy-controller-loader!./controllers', + true, + /\.(j|t)sx?$/ + )); + + // register any custom, 3rd party controllers here + // app.register('some_controller_name', SomeImportedController); + +And finally, create an ``assets/controllers.json`` file, which also fits into +the Stimulus system: + +```json +{ + "controllers": [], + "entrypoints": [] +} +``` + You'll customize and learn more about these files in :doc:`/frontend/encore/simple-example`. +When you execute Encore, it will ask you to install a few more dependencies based +on which features of Encore you have enabled. .. caution:: diff --git a/frontend/encore/page-specific-assets.rst b/frontend/encore/page-specific-assets.rst deleted file mode 100644 index 8f03bfb5877..00000000000 --- a/frontend/encore/page-specific-assets.rst +++ /dev/null @@ -1,27 +0,0 @@ -Creating Page-Specific CSS/JS -============================= - -If you're creating a single page app (SPA), then you probably only need to define -*one* entry in ``webpack.config.js``. But if you have multiple pages, you might -want page-specific CSS and JavaScript. - -To learn how to set this up, see the :ref:`multiple-javascript-entries` example. - -Multiple Entries Per Page? --------------------------- - -Typically, you should include only *one* JavaScript entry per page. Think of the -checkout page as its own "app", where ``checkout.js`` includes all the functionality -you need. - -However, it's pretty common to need to include some global JavaScript and CSS on -every page. For that reason, it usually makes sense to have one entry (e.g. ``app``) -that contains this global code (both JavaScript & CSS) and is included on every -page (i.e. it's included in the *layout* of your app). This means that you will -always have one, global entry on every page (e.g. ``app``) and you *may* have one -page-specific JavaScript and CSS file from a page-specific entry (e.g. ``checkout``). - -.. tip:: - - Be sure to use :doc:`split chunks ` - to avoid duplicate and shared code between your entry files. diff --git a/frontend/encore/simple-example.rst b/frontend/encore/simple-example.rst index 8fa55913d69..64115878107 100644 --- a/frontend/encore/simple-example.rst +++ b/frontend/encore/simple-example.rst @@ -1,11 +1,14 @@ Encore: Setting up your Project =============================== -After :doc:`installing Encore `, your app already has one -CSS and one JS file, organized into an ``assets/`` directory: +After :doc:`installing Encore `, your app already +has a few files, organized into an ``assets/`` directory: * ``assets/app.js`` +* ``assets/bootstrap.js`` +* ``assets/controllers.json`` * ``assets/styles/app.css`` +* ``assets/controllers/hello_controller.js`` With Encore, think of your ``app.js`` file like a standalone JavaScript application: it will *require* all of the dependencies it needs (e.g. jQuery or React), @@ -19,11 +22,14 @@ application: it will *require* all of the dependencies it needs (e.g. jQuery or import './styles/app.css'; -Encore's job (via Webpack) is simple: to read and follow *all* of the ``require()`` +Encore's job (via Webpack) is simple: to read and follow *all* of the ``import`` statements and create one final ``app.js`` (and ``app.css``) that contains *everything* your app needs. Encore can do a lot more: minify files, pre-process Sass/LESS, support React, Vue.js, etc. +The other files - ``bootstrap.js``, ``controllers.json`` and ``hello_controller.js`` +relate to a topic you'll learn about soon: `Stimulus & Symfony UX`_. + Configuring Encore/Webpack -------------------------- @@ -59,27 +65,24 @@ To build the assets, run the following if you use the Yarn package manager: .. code-block:: terminal - # compile assets once - $ yarn encore dev - - # or, recompile assets automatically when files change - $ yarn encore dev --watch + # compile assets and automatically re-compile when files change + $ yarn watch - # on deploy, create a production build - $ yarn encore production + # if using npm, use "npm run" and then any of these commands + $ npm run watch -If you use the npm package manager, run the following commands instead: - -.. code-block:: terminal + # or, run a dev-server that can sometimes update your code without refreshing the page + $ yarn dev-server # compile assets once - $ npm run dev - - # or, recompile assets automatically when files change - $ npm run watch + $ yarn dev # on deploy, create a production build - $ npm run build + $ yarn build + +All of these commands - e.g. ``dev`` or ``watch`` - are shortcuts that are defined +in your ``package.json`` file. If you use the npm package manager, replace ``yarn`` +with ``npm run``. .. note:: @@ -91,8 +94,15 @@ Congrats! You now have three new files: * ``public/build/app.css`` (holds all the CSS for your "app" entry) * ``public/build/runtime.js`` (a file that helps Webpack do its job) -Next, include these in your base layout file. Two Twig helpers from WebpackEncoreBundle -can do most of the work for you: +.. note:: + + In reality, you probably have a few *more* files in ``public/build``. Some of + these are due to :doc:`code splitting `, an optimization + that helps performance, but doesn't affect how things work. Others help Encore + do its work. + +Next, to include these in your base layout, you can leverage two Twig helpers from +WebpackEncoreBundle: .. code-block:: html+twig @@ -130,7 +140,7 @@ That's it! When you refresh your page, all of the JavaScript from be executed. All the CSS files that were required will also be displayed. The ``encore_entry_link_tags()`` and ``encore_entry_script_tags()`` functions -read from an ``entrypoints.json`` file that's generated by Encore to know the exact +read from a ``public/build/entrypoints.json`` file that's generated by Encore to know the exact filename(s) to render. This file is *especially* useful because you can :doc:`enable versioning ` or :doc:`point assets to a CDN ` without making *any* changes to your @@ -155,7 +165,7 @@ Requiring JavaScript Modules ---------------------------- Webpack is a module bundler, which means that you can ``import`` other JavaScript -files. First, create a file that exports a function: +files. First, create a file that exports a function, class or any other value: .. code-block:: javascript @@ -196,14 +206,121 @@ That's it! If you previously ran ``encore dev --watch``, your final, built files have already been updated: jQuery and ``greet.js`` have been automatically added to the output file (``app.js``). Refresh to see the message! +Stimulus & Symfony UX +--------------------- + +As simple as the above example is, instead of building your application inside of +``app.js``, we recommend `Stimulus`_: a small JavaScript framework that makes it +easy to attach behavior to HTML. It's powerful, and you will love it! Symfony +even provides packages to add more features to Stimulus. These are called the +`Symfony UX Packages`_. + +If you followed the setup instructions, you should already have Stimulus installed +and ready to go! In fact, that's the purpose of the ``assets/bootstrap.js`` file: +to initialize Stimulus and automatically load any "controllers" from the +``assets/controllers/`` directory. + +Let's look at a simple Stimulus example. In a Twig template, suppose you have: + +.. code-block:: twig + +
+ + + + +
+
+ +The ``stimulus_controller('say-hello')`` renders a ``data-controller="say-hello"`` +attribute. Whenever this element appears on the page, Stimulus will automatically +look for and initialize a controller called ``say-hello-controller.js``. Create +that in your ``assets/controllers/`` directory: + +.. code-block:: javascript + + // assets/controllers/say-hello-controller.js + import { Controller } from '@hotwired/stimulus'; + + export default class extends Controller { + static targets = ['name', 'output'] + + greet() { + this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!` + } + } + +The result? When you click the "Greet" button, it prints your name! And if +more ``{{ stimulus_controller('say-hello') }}`` elements are added to the page - like +via Ajax - those will instantly work: no need to reinitialize anything. + +Ready to learn more about Stimulus? + +* Read the `Stimulus Documentation`_ +* Check out the `Symfony UX Packages`_ +* Learn more about the `Symfony Stimulus Bridge`_ - including the superpower of + making your controllers load lazily! + + .. admonition:: Screencast + :class: screencast + + Or check out the `Stimulus Screencast`_ on SymfonyCasts. + +Turbo: Lightning Fast Single-Page-Application Experience +-------------------------------------------------------- + +Symfony comes with tight integration with another JavaScript library called `Turbo`_. +Turbo automatically transforms all link clicks and form submits into an Ajax call, +with zero (or nearly zero) changes to your Symfony code! The result? You get the +speed of a single page application without having to write any JavaScript. + +To learn more, check out the `symfony/ux-turbo`_ package. + +.. admonition:: Screencast + :class: screencast + + Or check out the `Turbo Screencast`_ on SymfonyCasts. + +Page-Specific JavaScript or CSS +------------------------------- + +So far, you only have one final JavaScript file: ``app.js``. Encore may be split +into multiple files for performance (see :doc:`split chunks `), +but all of that code is still downloaded on every page. + +What if you have some extra JavaScript or CSS (e.g. for performance) that you only +want to include on *certain* pages? + +Lazy Controllers +~~~~~~~~~~~~~~~~ + +One very nice solution if you're using Stimulus is to leverage `lazy controllers`_. +To activate this on a controller, add a special ``stimulusFetch: 'lazy'`` above +your controller class: + +.. code-block:: javascript + + // assets/controllers/lazy-example-controller.js + import { Controller } from '@hotwired/stimulus'; + + /* stimulusFetch: 'lazy' */ + export default class extends Controller { + // ... + } + +That's it! This controller's code - and any modules that it imports - will be +split to *separate* files by Encore. Then, those files won't be downloaded until +the moment a matching element (e.g. ``
``) +appears on the page! + .. _multiple-javascript-entries: -Page-Specific JavaScript or CSS (Multiple Entries) --------------------------------------------------- +Multiple Entries +~~~~~~~~~~~~~~~~ -So far, you only have one final JavaScript file: ``app.js``. For small applications -or SPA's (Single Page Applications), that might be fine! However, as your app grows, -you may want to have page-specific JavaScript or CSS (e.g. checkout, account, +Another option is to create page-specific JavaScript or CSS (e.g. checkout, account, etc.). To handle this, create a new "entry" JavaScript file for each page: .. code-block:: javascript @@ -234,7 +351,7 @@ and restart Encore: .. code-block:: terminal # if you use the Yarn package manager - $ yarn encore dev --watch + $ yarn watch # if you use the npm package manager $ npm run watch @@ -263,10 +380,9 @@ you need them: Now, the checkout page will contain all the JavaScript and CSS for the ``app`` entry (because this is included in ``base.html.twig`` and there is the ``{{ parent() }}`` call) -*and* your ``checkout`` entry. - -See :doc:`/frontend/encore/page-specific-assets` for more details. To avoid duplicating -the same code in different entry files, see :doc:`/frontend/encore/split-chunks`. +*and* your ``checkout`` entry. With this, JavaScript & CSS needed for every page +can live inside the ``app`` entry and code needed only for the checkout page can +live inside ``checkout``. Using Sass/LESS/Stylus ---------------------- @@ -347,3 +463,12 @@ Encore supports many more features! For a full list of what you can do, see .. _`Encore's index.js file`: https://github.com/symfony/webpack-encore/blob/master/index.js .. _`WebpackEncoreBundle Configuration`: https://github.com/symfony/webpack-encore-bundle#configuration +.. _`Stimulus`: https://stimulus.hotwired.dev/ +.. _`Stimulus Documentation`: https://stimulus.hotwired.dev/handbook/introduction +.. _`Symfony UX Packages`: https://github.com/symfony/ux +.. _`Symfony Stimulus Bridge`: https://github.com/symfony/stimulus-bridge +.. _`Turbo`: https://turbo.hotwired.dev/ +.. _`symfony/ux-turbo`: https://github.com/symfony/ux/tree/2.x/src/Turbo +.. _`Stimulus Screencast`: https://symfonycasts.com/screencast/stimulus +.. _`Turbo Screencast`: https://symfonycasts.com/screencast/turbo +.. _`lazy controllers`: https://github.com/symfony/stimulus-bridge#lazy-controllers