Creating custom components for Bootstrap 4


Public opinion has turned Bootstrap into the category of legendary frameworks of the past, but it’s still worth watching. Bootstrap 4 is an excellent navigator for safe layout and, most importantly, a sample HTML over JS approach to creating web applications, fully reveals the existing HTML capabilities for a declarative description of the user interface.


And how the JavaScript code of the framework develops is also useful to have an idea. The jQuery plugin architecture is still in use, but from version 4 it is wrapped by Rollup 's ES6 transplanted classes using Babel6 . jQuery will probably not be there at all soon - more on that later - for now, using the example of creating your own BsMultiSelect plugin, on the same stack as Boostrap 4 , the features of the development of the framework will be revealed.


Why " every programmer should write his own ... "


" Improve the good only spoil ." Defeated the need to understand that Bootstrap received a new one by transferring its components to a new stack, and how much easier it is to create components of a higher order now. It seemed that everything should be simple: combine .form-control , .badge and .dropdown-menu and you will easily get a multi .dropdown-menu ...



Conflict of architectures from the threshold


The resulting BsMultiSelect plugin directly, JS, does not use the bootstrap.js code, but uses its dependency, popper.js (the vanilla popup elements framework). This is because the dropdown component from bootstrap.js needs to find the so-called toggle element during initialization in the DOM (in the particular case it is the button that opens the menu), if it does not find it, it drops with an error (since I create directly from js then the "button" in me of course not). Conclusion: components sharpened under "HTML over JS" can not (or disadvantageously) be created from JS directly. Conclusion number two: it is necessary to program user components so that under the "HTML over JS" component, there will always be a lighter component of the "pure JS". I have this MulitSelect.js completely cleared not only from the code but also from the styles of Bootstrap. MulitSelect.js plays a tighter role for BsMulitSelect.js, as popper.js for dropdown.js.


Two words about popper.js


Excellent library - one function. DropDown.js repeats but provides a very limited interface to it (again, because the whole API is not needed in "HTML over JS"). Popper has more open issues than we would like. I was disappointed that under IE11 publishes events not as under Chrome .


About moving from IIFE to class


The standard jQuery Plugina boilerplate is the definition of a constructor function inside IIFE . Now this is a class inside IIFE. Discovery: Old functional ways to do private methods conflict with ES6 lambda so they are “abandoned” in Bootstrap. All methods are public, and "pseudo-private" is marked with the prefix _ i. underscrore I understand that it is not right, but I follow my agreements - "public methods with a capital letter." In all other respects, BsMultiSelect was created by adopting the agreement of the "standard" DropDown.js, i.e. I would like to represent him like a boilerplate with meat.


However, one more difference, since there is a rollup, I beat on the files ..
 import $ from 'jquery' import AddToJQueryPrototype from './AddToJQueryPrototype' import MultiSelect from './MultiSelect' // ... ( (window, $) => { AddToJQueryPrototype('BsMultiSelect', (element, optionsObject, onDispose) => { // ... return new MultiSelect(element, optionsObject, onDispose, facade, window, $); }, $); } )(window, $) 

The new agreement: the plugin must publish Constructor - this is not to lose the designer inside IIFE. Yes, one IIFE should remain - the standard task is to determine the dependencies of window, $ and Popper (for those who "cannot in modules") nobody has removed.


Eslint and Stylelint liner configurations


I did not feel obliged to follow the draconian rules for styling Bootstrap code. Surely they write in an environment that helps them in laying out gaps and for source control rigor is very good, but it is hard for me every second to listen to the linter's grief in VS Code. When writing your own plug-in rules can and should be greatly weakened.


Eslint, stylelint may not come out too often, but there is no desire to look through the new rules. In all aspects, this achievement annoys me more.


Code organization and rollup


The translated ES6 code of Bootstrap is packaged by a rollup in two versions: standalone bootstrap.js and bundle bootstrap.bundle.js - the latter includes popper.js.


The author of rollup.config.js has alien instincts to me. All this configuration transformation via push and pop, depending on the required version of standalone or bundle, looks pretentious and frightening, fortunately for writing the plug-in you only need one type of standlone bundle and I don’t have to prove that I can do better.


Standalone version of Bootstrap is indicated in the main field of the package i. npm dependencies are resolved to the same file that users load via <script> . This is not such an unambiguous solution, but my BsMultiSelect plugin followed the same path. In this case babel helpers can be included in the plugin , despite the fact that they are also included in bootsrap.js. Duplication is somehow wrong ...


What is interesting is that Bootstrap in parallel has a jspm configuration — the native browser ES module loader and its main field is already a clean transfigured code, 12 components, not merged into a bundle, but stored separately in the js / dist directory. Here, each file includes babel helper'y. Jspm is now at a crossroads so I’m ignoring it when creating BsMultiselect.


And yet it is interesting to ask the experts, is it possible that jspm is going to raise all 12 times the duplicated helpers of a Babel into a browser?

Discovered a big plus rollup'a - its packages are readable in contrast to the packages webpack'a


Bootsrap 4 uses Babel 6 and BsMultiSelect Babel 7 plugin


BsMultiSelect easily migrated to the Babel 7 beta with the updated rollup-babel-plugin (also in beta). On Babel 7, it is worth moving now because of the new @babel/preset-env (transpiler configuration "on which browsers the code will run on"). It’s pretty hard to figure out how to work in ES6 presets using the Internet; they have gone through a long evolution and a lot of outdated configurations have accumulated. It's easier to put Babel 7 + @babel/preset-env beta right away


Nevertheless, I did not have to wait long for the errors of the babel’s beta: [... nodeList] was tangled in nodeList.concat (). I didn’t understand for a long time, translating the code into jquery calls (it’s still necessary, but more on that later) solved the problems. Ask: "Why, then, in general, Babel if you do not understand him?", The answer is: "Imports and lambda."


Practical aspects


Bootstrap 4 and its plug-in (like any js code) have two options for loading into the browser: via script / style (create a "page"), and through the assembly node / webpack (create an "application", so I will continue these two In principle, very different situations and designate: "page" and "application").
For the page and for the application, the same “distribution file” Bootstrap will be used. This is not an obvious and not the only solution (as an example, other code is used for jspm and for mocha tests), but by creating your own plugin, there is nothing else left but to follow this agreement, hoping for its optimality.


Finding a balance between satisfying two needs, using the same code for two different download options: "pages" and "applications" - is the main architectural task that a plug-in developer faces.


The plug-in to Bootstrap can be created ad hoc directly in your application. In this case, you can write the plug-in code with all the pleasures: polyfiles and access to SASS variables of the scheme. However, when, based on reuse, you need to transfer the plug-in to its own npm package, the listed pleasures become problems: you lose access to SASS variables and you must keep in mind the creators of the pages connecting the plugin via script (where are my polyfiles?).


Access to SASS Variables Bootstrap 4 Style


I would like to write a plugin enough css classes / selectors Bootstrap'a. In this case, when customizing the Bootstrap theme, you can expect the plug-in to adapt without additional efforts.


For self-published plug-in access to SASS variables is not.


Existing css classes / selectors - do not cover all necessary styles for BsMutliSelect, for example, you need min-height for ul.form-control , when only css selector with height for input.form-control exists in bootstrap.css - by no means you will not apply it for ul . Other existing classes / selectors bring a lot of extra styles: what if you want for example to assign a color from .form-control to an element without assigning a class .form-control ? This will not work because there is no such class as .input-color in which the $ input-color variable would be published “separately” from everything.


It’s not possible to follow the decisions of the Bootstrap team: they don’t have such a problem, if they need to - they create their own CSS class / selector with the desired variable and operate on it. If you want to debate, you will have to explain yourself with Mark Otto , in the BS command - an abusrno high level of decision making, even for cosmetic changes in CSS selectors, if requests fall into the category of "offer".


I propose the following solution.


For a page (raising a plugin through a script ), the inaccessibility of variables has to be accepted. But it is not necessary to offer to deal with CSS - inaccessible variables can be offered to customize through the js parameters, since there are not many of them (and not all of them are changed by any scheme). For BsMultiSelect, the problematic ones were: color disabled, color input, focused control shadow (etc. there are only 10 styles, most of them rarely change circuits) ...


For this one is the declaration of all styles on which the plugin depends ...
 $("select[multiple='multiple']").bsMultiSelect({ selectedPanelDefMinHeight: 'calc(2.25rem + 2px)', // default size selectedPanelLgMinHeight: 'calc(2.875rem + 2px)', // LG size selectedPanelSmMinHeight: 'calc(1.8125rem + 2px)', // SM size selectedPanelDisabledBackgroundColor: '#e9ecef', // disabled background selectedPanelFocusBorderColor: '#80bdff', // focus border selectedPanelFocusBoxShadow: '0 0 0 0.2rem rgba(0, 123, 255, 0.25)', // foxus shadow selectedPanelFocusValidBoxShadow: '0 0 0 0.2rem rgba(40, 167, 69, 0.25)', // valid foxus shadow selectedPanelFocusInvalidBoxShadow: '0 0 0 0.2rem rgba(220, 53, 69, 0.25)', // invalid foxus shadow inputColor: '#495057', // color of keyboard entered text selectedItemContentDisabledOpacity: '.65' // btn disabled opacity used }); 

But for an application using SASS and a collector, you can offer to copy BsMultiSelect.scss to yourself, and correct the path to your _variables.scss in it. Thus, the plugin will “get” the schema variables.


All through CSS ...
  $("select[multiple='multiple']").bsMultiSelect({ useCss: true }); 

These two methods are written in my JS code explicitly, through two “adapters”. BsMultiSelect is actually two plugins in one. One that works with styles via js variables, the other delegates this work to css.


At the same time, it must be said explicitly that working with CSS may be needed even if the transfer of customized styles through JS parameters was used, simply because the authors of the schemes usually do their work very roughly. Or strange. For example, the Skethy scheme for some reason decided that the menu does not need a hover effect. Features must be processed with a file.


But also using the example of sketchy, I hope you can see how the plug-in adapted relatively simply to a heavy scheme (it was necessary to overload the values ​​of all three styles through the js parameters).


adaptation under the scheme through js
 $("select[multiple='multiple']").bsMultiSelect({ selectedPanelFocusBoxShadow:"0px 0px 0px 0.2rem rgba(51,51,51,0.25)", selectedPanelFocusBorderColor:"#333", selectedPanelDisabledBackgroundColor: "#f7f7f9" }); 


This is the maximum of what can be achieved using the "standard" CSS classes. .bage , .form-control , .close , etc. Almost everything adapted to the scheme, but not all.


It’s a pity, of course, that you don’t get away from these three parameters, not all schema style variables are available through css selectors / classes (I’m lying, you can leave BsMultiSelect.scss to copy to yourself and give it variable schemas, but this doesn’t completely satisfy as all decisions that begin with the words "copy to yourself").


You can click here: https://dashboardcode.imtqy.com/BsMultiSelect/indexsketchy.html


Polyfills


In the “write ad hoc plugin inside the application” scenario, you will be able to use @babel/polyfill without any problems, and DOM patch polyfill.io because inside your application, this is all already there.


When the plug-in is moved to the external npm module, all the joy disappears because it is not customary for the users of Bootstrap to please with long lists of required polyfills in the documentation.


Solution: all that babel will not transpill but will leave to polyfiles, prevent it from doing this, gritting his teeth, and redoing it into jQuery calls (for example, Node.closest in $ .closest). This approach is adopted in the very Bootstrap 4 and obviously it is a struggle with swelling. Surprise. jQuery is a polyfil.


I agree that programming Babel with an eye on CanIUse.com and MDN.com does not give pleasure. Yes, and the code is awkward (I have it).


Here it is necessary to highlight the situation from above again. Bootstrap uses two jQuery and popper.js libraries. Popper.js is a double vanilla framework, there is no babel, no jQuery, no external polyfills in it, all by itself. JQuery is packaged by grunt already using babel, but in some magical way without its polyfills and helperes. The third-party Bootstrap plugin (like BsMultiSelct) uses Babel 7. Your product may use something else. There is no unified platform for polyfiles, multiple code duplication is expected, four implementations of the same closest are loaded into the browser. But nothing can be done about it.


If you still use polyfiles when creating your own plugin, you need to remember that if the application has inline scripts (i.e., <script>$( function() {/*..*/}) </script> ), then it is only necessary to load polyfiles synchronously (otherwise the inline code can start execution before the plug-in is loaded). Those. for example, webpack-polyfill-injector (the richest in possibilities from me known injector polyfiles) must be controlled so that he does not decide to load polyfiles asynchronously (default behavior).


No jQuery


jQuery feels something toxic, competing with Babel / ES6 and shading it, but it's not forever. There is a branch v4-whithout-jquery , it is already in pool request, with plans to release bootstrap-nojquery from version 4.3 . Previously, the biggest problem of abandoning jquery was the namespace of events, although it is obvious that you can write without them (BsMultiSelect managed). Apparently decided.


It is also announced that from version 4.3 it will be possible to load original components into the browser separately (only for the “application” scenario). Will the original components "like BsMultiSelect " become then ? Not. If the BS team needs some SASS variables, they will customize CSS classes / selectors and will operate on them from JS ("functional CSS"). For creators of custom components, such features are not available.


A possible transition from sass to post-css https://github.com/twbs/bootstrap/projects/11 in version 5 does not fundamentally change anything. Or it changes everything: "JS over HTML".


Bootstrap 5 will have to set recommendations on how to integrate into Babel polyfiles, and how to patch the DOM. Here the forecast is more or less clear: the creators of the pages and the creators of the applications will be offered a completely different assembly.


Results


As long as you write the components Bootstrap 4 on Babel using the ad hoc method without thinking about reuse, which means about polyfiles, and you don’t know where to take the style variables.


But if you make a plugin as a separate npm package - and with the desire to make a decision in the spirit of Bootstrap 4 itself, the pleasure becomes less, you think more often and longer. You will probably have to decide right away who your users are and offer different builds, one for those who will build the application in the bundle, another for the writing pages, i.e. loading plugin script'om.

Source: https://habr.com/ru/post/416235/


All Articles