Example of using JSX to create a Markup component style development (front-end componentization)

Example of using JSX to create a Markup component style development (front-end componentization)

Here we build a component system from scratch. First of all, we know from the previous articles "Basics of Front-end Componentization" and "Using JSX to Establish Component Parser" that a component is an environment that can be accessed through Markup and JavaScript.

So our first step is to create an environment where we can use markup. Here we will learn how to use JSX to style our markup. Here we use JSX, the same as React, to style our components.


JSX environment construction

In general, JSX is considered to be a part of React. In fact, Facebook defines JSX as a pure language extension. And this JSX can also be used by other component systems.

We can even use it as a way to quickly create HTML tags.

Setting up the project

So let's start from the basics. First we need to create a new project directory:

mkdir jsx-component

Initialize NPM

Of course, create this project folder in the directory you like. After creating the folder, we can enter the directory and initialize npm .

npm init

After executing the above command, some project configuration options will appear. You can fill them in if necessary. However, we can also just keep pressing Enter, and then students who need it can open package.json and modify it themselves later.

Install webpack

Many students should have known about Wepack, which can help us turn an ordinary JavaScript file into a file that can package different import and require files together.

So we need to install webpack . Of course, we can also use npx to use webpack directly, or we can install webpack-cli globally.

So here we use the global installation of webpack-cli:

npm install -g webpack webpack-cli

After the installation is complete, we can check the installed webpack version by entering the following command. If there is no error after execution and a version number appears, it proves that we have installed it successfully.

webpack --version

Install Babel

Because JSX is a babel plugin, we need to install webpack, babel-loader, babel and babel plugin in sequence.

Another use of Babel here is that it can compile a new version of JavaScript into an old version of JavaScript, so that we can support running in more older versions of browsers.

To install Babel we just need to execute the following command.

npm install --save-dev webpack babel-loader

What we need to note here is that we need to add --save-dev so that we will add babel to our development dependencies.

After execution, we should see the message shown in the image above.

To verify that we have installed it correctly, we can open package.json in our project directory.

{
 "name": "jsx-component",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
 "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
 "babel-loader": "^8.1.0",
 "webpack": "^5.4.0"
 }
}

OK, we can see that under devDependencies , there are indeed the two packages we just installed. If you are still worried, you can check package.json again.

Configure webpack

At this point we need to configure the webpack configuration. Configure webpack We need to create a webpack.config.js configuration file.

Create a webpack.config.js file in the root directory of our project.

First of all, webpack config is a nodejs module, so we need to use module.exports to write its settings. This is a common configuration method for early nodejs tools. It uses a JavaScript file to configure it, so that some logic can be added to the configuration.

module.exports = {}

The most basic thing about Webpack is that you need to set up an entry (set its entry file). Here we just set up a main.js

module.exports = {
 entry: "./main.js"
}

At this time, we can first create our main.js file in our root directory. Let's first add a simple for loop inside.

// main.js file content for (let i of [1, 2, 3]) {
 console.log(i);
}

In this way, the basic configuration of our webpack is configured. Let's execute webpack in the root directory to package our main.js file and see. We just need to execute the following command:

webpack 

After the execution is completed, we can see a prompt like the above in the command line interface.

Students who pay attention to details will definitely raise their hands and ask, classmates! There is an error in your command line! The yellow part does give us a warning, but it doesn’t matter, our next configuration will fix it.

At this time we will find that a new folder dist is generated in our root directory. This is the folder generated by webpack by default. All our packaged JavaScript and resources will be placed in this folder by default.

Here we will find that there is a packaged main.js file in the dist folder. This is main.js we wrote, which is packaged by webpack.

Then we open it and we will see the JavaScript code after it is compiled by babel. We will find that a lot of things have been added to our few lines of code, but we don’t need to worry about these things, this is the “meow power” of Webpack.

At the end of the code, you can still see for loop we wrote, it has just been modified a bit, but its function is the same.

Install Babel-loader

Next, let's install babel-loader. In fact, babel-loader does not directly depend on babel, so we need to install @babel/core and @babel/preset-env separately. We just need to execute the following command line to install it:

npm install --save-dev @babel/core @babel/preset-env 

The final result is as shown above, proving that the installation was successful. At this time we need to configure it in webpack.config.js so that we can use babel-loader when packaging.

Add an option called module after entry of webpack.config.js that we configured above.

Then we can also add a rules to the module, which is the rule we use when building. rules is an array type configuration, and each rule here consists of a test and a use .

  • test:

The value of test is a regular expression that is used to match the files we need to use this rule for. Here we need to match all JavaScript files, so we can use /\.js/ .

  • use:

loader: Just add the name of our babel-loader

  • options:

presets:

  • Here are the loader options, here we need to add @babel/preset-env

Finally our configuration file will look like this:

module.exports = {
 entry: './main.js',
 module: {
 rules:
 {
 test: /\.js$/,
 use: {
 loader: 'babel-loader',
 options:
 presets: ['@babel/preset-env'],
 },
 },
 },
 ],
 },
};

After configuring this, we can run babel to try it out. As before, we just need to execute webpack on the command line.

If our configuration file is written correctly, we should see the results shown in the figure above.

Then we go to dist folder, open our compiled main.js , and take a look at the compilation result after we used babel-loader this time.

After compilation, we will find that for of of loop is compiled into a normal for loop. This also proves that our babel-loader is effective and correctly programs our new version of JavaScript syntax into JavaScript syntax that is compatible with older browsers.

At this point we have installed and built the environment required for our JSX.

Mode Configuration

Finally, we also need to add an environment configuration in webpack.config.js, but this can be said to be optional, but we do it for the convenience of daily development.

So we need to add a mode in webpack.config.js, and we use development as the value of this property. This configuration indicates that we are in developer mode.

Generally speaking, the webpack configuration we write in the code repository will add this mode: 'development' configuration by default. When we actually release it, we will change it to mode: 'production' .

module.exports = {
 entry: './main.js',
 mode: 'development',
 module: {
 rules:
 {
 test: /\.js$/,
 use: {
 loader: 'babel-loader',
 options:
 presets: ['@babel/preset-env'],
 },
 },
 },
 ],
 },
};

After the modification, we use webpack to compile and see what the difference is in our main.js

Obviously we found that the compiled code is not compressed into one line. This way we can debug the code generated by webpack. Here we can notice that our code in main.js is converted into a string and put into an eval() function. Our code is put into eval , so we can use it as a separate file when debugging.

Importing JSX

Everything is ready, except for the east wind. Finally, how do we introduce JSX? Before importing, let's see what happens if we use the JSX syntax in our main.js with the current configuration. As programmers, we always have to be a little adventurous!

So we add this code to our main.js :

var a = <div/>

Then let's run webpack and see!

Good job! Sure enough, it reported an error. The error here tells us that the "less than sign" cannot be used after = , but in normal JSX syntax, this is actually the "angle brackets" of the HTML tag. Because there is no JSX syntax compilation process, JavaScript will assume that this is the "less than sign" by default.

So what should we do to make our webpack compilation process support JSX syntax? Here we actually need to add a most critical package, and the name of this package is very long, called @babel/plugin-transform-react-jsx . Well, let's execute a command to install this package:

npm install --save-dev @babel/plugin-transform-react-jsx

After installation, we also need to add it to the webpack configuration. We need to add ['@babel/plugin-transform-react-jsx'] to the plugins configuration in use in rules in module .

Then finally our webpack configuration file looks like this:

module.exports = {
 entry: './main.js',
 mode: 'development',
 module: {
 rules:
 {
 test: /\.js$/,
 use: {
 loader: 'babel-loader',
 options:
 presets: ['@babel/preset-env'],
 plugins: ['@babel/plugin-transform-react-jsx'],
 },
 },
 },
 ],
 },
};

After configuration, let's execute webpack. At this time we found that there was no more error. This proves that our code now supports writing using JSX syntax.

Finally, let’s take a look at the final effect of the programming.

We will find that <div/> we added in eval is translated into a function call of React.createElement("div", null) .

So let's take a look at how we should implement React.createElement and whether we can replace it with our own function name.

Basic JSX usage

First, let's try to understand JSX. JSX is actually just a shortcut in code syntax. At the end of the previous section, we saw that after the JSX syntax is compiled, a call to React.createElement will appear.

JSX Basics

So here we first modify the JSX plug-in in webpack and give it a custom element creation function name. We open webpack.config.js and modify it in the plugins section.

module.exports = {
 entry: './main.js',
 mode: 'development',
 module: {
 rules:
 {
 test: /\.js$/,
 use: {
 loader: 'babel-loader',
 options:
 presets: ['@babel/preset-env'],
 plugins: [
				[
					'@babel/plugin-transform-react-jsx',
					{ pragma: 'createElement' }
				]
			],
 },
 },
 },
 ],
 },
};

Above we just changed the original ['@babel/plugin-transform-react-jsx'] parameter [['@babel/plugin-transform-react-jsx', {pragma: 'createElement'}]] . By adding this pragma parameter, we can customize the function name of the element we create.

With this change, our JSX has nothing to do with the React framework. Let's execute webpack and look at the final generated effect. We will find that React.createElement will become createElement .

Next, we add an HTML file to execute our main.js and try it out. First create a main.html in the root directory and enter the following code:

<script src="./main.js"></script>

Then we execute and open this HTML file in the browser.

At this time, our console will throw us an error, and our createElement is undefined. Indeed, we haven't defined this function in main.js , so it can't be found.

So we need to write a createElement function ourselves. We directly open main.js in the root directory and delete the previous for loop, then add the following code:

function createElement() {
 return;
}

let a = <div />;

Here we just return empty and make this function callable first. We recompile once with webpack and then refresh our main.html page. At this time we will find that there is no error and it can run normally.

Implementing the createElement function

In our compiled code, we can see that the JSX element passes two parameters when calling createElement. The first argument is div , the second is null .

Why is the second parameter null here? In fact, the second parameter is used to pass the attribute list. If we add an id="a" to the div in main.js, let's see what changes will occur in the final compilation.

We will find that the second parameter becomes a JavaScript object stored in Key-Value mode. If we think about it now, JSX is not that mysterious. It just compiles the HTML we usually write into a JavaScript object. We can think of it as a kind of "syntactic sugar".

But JSX affects the structure of the code, so we generally don't call it syntactic sugar.

Next, let's write some more complex JSX. We'll add some children elements to our original div.

function createElement() {
 return;
}

let a = (
 <div id="a">
 <span></span>
 <span></span>
 <span></span>
 </div>
);

Finally, let's execute webpack packaging to see the effect.

In the console, we can see that the final compiled result is a recursive call to the createElement function. In fact, a tree structure has been formed here.

The parent is the first-level div element, and the child is the parameter passed into the first createElement function. Then, because our spans have no attributes, the second parameter of all subsequent createElements is null .

Based on the compilation result we see here, we can analyze what the parameters of our createElement function should be.

  • The first parameter type - is the type of this tag
  • The second parameter attribute - all attributes and values ​​in the tag
  • The remaining parameters are all child properties ...children - Here we use a relatively new syntax in JavaScript ...children means that all the following parameters (indefinite number) will be turned into an array and assigned to the children variable

Then our createElement function can be written like this:

function createElement(type, attributes, ...children) {
 return;
}

We have the function, but what can this function do? In fact, this function can be used to do anything. Because it looks like a DOM API, we can make it into an entity DOM that has nothing to do with React.

For example, we can return the element of this type in this function. Here we add all the passed attributes to this element, and we can attach child elements to this element.

To create an element we can use createElement(type) , to add attributes we can use setAttribute() , and finally to attach child elements we can use appendChild() .

function createElement(type, attributes, ...children) {
 // Create an element let element = document.createElement(type);
 //Hang attributes for (let attribute in attributes) {
 element.setAttribute(attribute);
 }
 //Hang all child elements for (let child of children) {
 element.appendChild(child);
 }
 //Finally our element is a node// so we can return directly return element;
}

Here we implement the logic of createElement function. Finally, we also need to mount our DOM node on the page. So we can mount it directly on the body.

// Add this code to the end of main.js let a = (
 <div id="a">
 <span></span>
 <span></span>
 <span></span>
 </div>
);

document.body.appendChild(a);

It should also be noted here that there is no body tag in our main.html. Otherwise, we cannot mount it on the body. So here we need to add the body tag to main.html.

<body></body>
<script src="dist/main.js"></script>

OK, now we can package it with webpack and see the effect.

Wonderful! We have successfully generated the node and attached it to the body. But if we add a piece of text to our div , a text node will be passed into our createElement function. Needless to say, our createElement function cannot handle text nodes with its current logic.

Next we will add the logic for processing text nodes, but before that we will delete the span tag in the div and replace it with a text "hello world".

let a = <div id="a">hello world</div>;

Before we add the logic of the text node, let's package it with webpack first. Before we hang up the child node, let's see what specific errors will be reported.

First, we can see that where createElement function is called, our text is passed in as a string, and then this parameter receives the child node, and in our logic we use appendChild , which receives the DOM node. Obviously our text string is not a node, so an error will be reported.

Through this debugging method, we can immediately locate where we need to add logic to implement our function. This method can also be considered a shortcut.

So let's go back to main.js Before we attach the child node, we determine the type of child. If its type is a "String" string, we use createTextNode() to create a text node and then attach it to the parent element. In this way, we have completed the processing of character nodes.

function createElement(type, attributes, ...children) {
 // Create an element let element = document.createElement(type);
 //Hang attributes for (let name in attributes) {
 element.setAttribute(name, attributes[name]);
 }
 //Hang all child elements for (let child of children) {
 if (typeof child === 'string') 
		child = document.createTextNode(child);
 element.appendChild(child);
 }
 //Finally our element is a node// so we can return directly return element;
}

let a = <div id="a">hello world</div>;

document.body.appendChild(a);

After we use this latest code webpack to package, we can see our text is displayed on the browser.

At this point, createElement is already a useful thing, and we can use it to do some DOM operations. It can even completely replace the repetitive and tedious operation of writing document.createElement ourselves.

Here we can verify that we add our previous three spans back into the div and add text to each span. 11

let a = (
 <div id="a">
 hello world:
 <span>a</span>
 <span>b</span>
 <span>c</span>
 </div>
);

Then after we re-pack the webpack, we can see that this DOM operation can indeed be completed.

The current code can already achieve certain basic component capabilities.

Implementing custom tags

Before, we were using some tags that came with HTML. What happens if we now change the d in div to a capital D?

let a = (
 <Div id="a">
 hello world:
 <span>a</span>
 <span>b</span>
 <span>c</span>
 </Div>
); 

As expected, it will report an error. But this is the key to finding the root cause of the problem. Here we find that when we change div to Div, the div passed to our createElement changes from the string 'div' to a Div class.

Of course, the Div class is not defined in our JavaScript, so an error indicating that Div is not defined will be reported. Knowing where the problem lies, we can solve it. First, we need to solve the undefined problem, so we first create a Div class.

// Add class Div {} after the createElment function

Then we need to do type judgment in createElement . If the type we encounter is a character type, we will handle it in the original way. If we encounter any other situation, we instantiate the passed type .

function createElement(type, attributes, ...children) {
 // Create element let element;
 if (typeof type === 'string') {
 element = document.createElement(type);
 } else {
 element = new type();
 }

 //Hang attributes for (let name in attributes) {
 element.setAttribute(name, attributes[name]);
 }
 //Hang all child elements for (let child of children) {
 if (typeof child === 'string') child = document.createTextNode(child);
 element.appendChild(child);
 }
 //Finally our element is a node// so we can return directly return element;
}

Here we have another question, is there any way we can make custom tags operate like our ordinary HTML tags? There is a way to do this in the latest version of the DOM standard. We just need to register the name and type of our custom tag.

However, in our current more secure browsing version, it is not recommended to do so. Therefore, when using our custom element, it is recommended that we write an interface ourselves.

First we need to create a tag class, which allows any tag to be mounted on our DOM tree like the elements of our previous ordinary HTML tags.

It will contain the following methods:

  • mountTo() —— Create an element node for later mounting to parent node
  • setAttribute() - attach all attributes to an element
  • appendChild() - attaches all child elements to an element

First, let's simply implement the mountTo method in our Div class. Here we also need to add setAttribute and appendChild methods to it, because there is logic for mounting attribute sub-elements in our createElement . If these two methods are not available, an error will be reported. But at this time we will not implement the logic of these two methods, and just leave the method content blank.

class Div {
 setAttribute() {}
 appendChild() {}
 mountTo(parent) {
 this.root = document.createElement('div');
 parent.appendChild(this.root);
 }
}

This is actually very simple. First, create a div element node for root attribute in the class, and then mount this node to the parent of this element. This parent is passed in as a parameter.

Then we can change our original body.appendChild code to use the mountTo method to mount our custom element class.

// document.body.appendChild(a);
a.mountTo(document.body);

With the current code, we package it with webpack to see the effect:

We can see that our Div custom element is correctly mounted on the body. But the span tags in Div are not mounted. If we want it to work like a normal div, we need to implement our setAttribute and appendChild logic.

Next, let's try to complete the remaining implementation logic. Before we start writing setAttribute and appendChild, we need to add a constructor to our Div class. Here we can create the element and proxy it to root .

constructor() {
 this.root = document.createElement('div');
}

Then the setAttribute method is actually very simple. Just use this.root and call setAttribute in the DOM API. The same is true for appendChild . Finally, our code is as follows:

class Div {
 // Constructor // Create DOM node constructor() {
 this.root = document.createElement('div');
 }
 // Mount the element's attributes setAttribute(name, attribute) {
 this.root.setAttribute(name, attribute);
 }
 // Mount element child element appendChild(child) {
 this.root.appendChild(child);
 }
 // Mount the current element mountTo(parent) {
 parent.appendChild(this.root);
 }
}

Let's package it with webpack to see the effect:

We can see that both div and span are successfully mounted on body. It also proves that our self-made div can work normally.

There is another problem here, because we finally called a.mountTo() . If our variable a is not a custom element, but our ordinary HTML element, they will not have the mountTo method at this time.

So here we also need to add a Wrapper class to ordinary elements so that they can maintain the standard format of our element class. It is also called a standard interface.

Let's first write an ElementWrapper class. The content of this class is actually basically the same as our Div. There are only two differences

When creating a DOM node, you can pass the current element name type to our constructor and use this type to create our DOM node. AppendChild cannot directly use this.root.appendChild , because all ordinary tags are changed to our custom class, so the logic of appendChild needs to be changed child.mountTo(this.root)

class ElementWrapper {
 // Constructor // Create DOM node constructor(type) {
 this.root = document.createElement(type);
 }
 // Mount the element's attributes setAttribute(name, attribute) {
 this.root.setAttribute(name, attribute);
 }
 // Mount element child element appendChild(child) {
 child.mountTo(this.root);
 }
 // Mount the current element mountTo(parent) {
 parent.appendChild(this.root);
 }
}

class Div {
 // Constructor // Create DOM node constructor() {
 this.root = document.createElement('div');
 }
 // Mount the element's attributes setAttribute(name, attribute) {
 this.root.setAttribute(name, attribute);
 }
 // Mount element child element appendChild(child) {
 child.mountTo(this.root);
 }
 // Mount the current element mountTo(parent) {
 parent.appendChild(this.root);
 }
}

We still have a problem here, that is, when we encounter a text node, it is not converted into our custom class. So we also need to write one for the text node, called TextWrapper .

class TextWrapper {
 // Constructor // Create DOM node constructor(content) {
 this.root = document.createTextNode(content);
 }
 // Mount the element's attributes setAttribute(name, attribute) {
 this.root.setAttribute(name, attribute);
 }
 // Mount element child element appendChild(child) {
 child.mountTo(this.root);
 }
 // Mount the current element mountTo(parent) {
 parent.appendChild(this.root);
 }
}

With these element class interfaces, we can rewrite the logic in our createElement . Just replace our original document.createElement and document.createTextNode with instantiating new ElementWrapper(type) and new TextWrapper(content) .

function createElement(type, attributes, ...children) {
 // Create element let element;
 if (typeof type === 'string') {
 element = new ElementWrapper(type);
 } else {
 element = new type();
 }

 //Hang attributes for (let name in attributes) {
 element.setAttribute(name, attributes[name]);
 }
 //Hang all child elements for (let child of children) {
 if (typeof child === 'string') 
		child = new TextWrapper(child);
 element.appendChild(child);
 }
 //Finally our element is a node// so we can return directly return element;
}

Then we package it with webpack and see.

Without any surprises, our entire element is mounted on the body normally. Similarly, if we change our Div back to div, it will also work properly.

Of course, we generally don't write a meaningless Div element. Here we will write the name of our component, such as Carousel , a component for a carousel.

Full code - please give me a ⭐️ if this helps you, thanks!

This concludes this article about using JSX to create a Markup component style development example (front-end componentization). For more related JSX scripts, JSX group component creation, and JSX style content, please search for previous articles on 123WORDPRESS.COM or continue browsing the related articles below. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • How to use jsx syntax in vue
  • Specific use of Vue component jsx syntax
  • Vue jsx usage guide and how to use jsx syntax in vue.js
  • Detailed explanation of how to use jsx to write vue components

<<:  Shell script nginx automation script

>>:  MySQL error: Deadlock found when trying to get lock; try restarting transaction solution

Recommend

Webpack file packaging error exception

Before webpack packaging, we must ensure that the...

Share the problem of Ubuntu 19 not being able to install docker source

According to major websites and personal habits, ...

Solution to Navicat Premier remote connection to MySQL error 10038

Remote connection to MySQL fails, there may be th...

A brief summary of vue keep-alive

1. Function Mainly used to preserve component sta...

Summary of MySQL Undo Log and Redo Log

Table of contents Undo Log Undo Log Generation an...

A brief discussion on mysql backup and restore for a single table

A. Installation of MySQL backup tool xtrabackup 1...

Full analysis of MySQL INT type

Preface: Integer is one of the most commonly used...

Example code for css3 to achieve scroll bar beautification effect

The specific code is as follows: /*Scroll bar wid...

Implementation of CSS circular hollowing (coupon background image)

This article mainly introduces CSS circular hollo...

Tutorial analysis of quick installation of mysql5.7 based on centos7

one. wget https://dev.mysql.com/get/mysql57-commu...

Using text shadow and element shadow effects in CSS

Introduction to Text Shadows In CSS , use the tex...