26,39 €
Almost a decade after the release of Node.js, the tooling used by frontend developers is fully embracing this cross-platform JavaScript runtime, which is sadly often limited to server-side web development. This is where this Node.js book comes in, showing you what this popular runtime has to offer and how you can unlock its full potential to create frontend-focused web apps.
You’ll begin by learning the basics and internals of Node.js, before discovering how to divide your code into modules and packages. Next, you’ll get to grips with the most popular package managers and their uses and find out how to use TypeScript and other JavaScript variants with Node.js. Knowing which tool to use when is crucial, so this book helps you understand all the available state-of-the-art tools in Node.js. You’ll interact with linters such as ESLint and formatters such as Prettier. As you advance, you’ll become well-versed with the Swiss Army Knife for frontend developers – the bundler. You’ll also explore various testing utilities, such as Jest, for code quality verification. Finally, you’ll be able to publish your code in reusable packages with ease.
By the end of this web development book, you’ll have gained the knowledge to confidently choose the right code structure for your repositories with all that you’ve learned about monorepos.
Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:
Seitenzahl: 237
Veröffentlichungsjahr: 2022
A compendium for modern JavaScript web development within the Node.js ecosystem
Florian Rappl
BIRMINGHAM—MUMBAI
Copyright © 2022 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Group Product Manager: Pavan Ramchandani
Publishing Product Manager: Bhavya Rao
Senior Editor: Divya Anne Selvaraj
Technical Editor: Joseph Aloocaran
Copy Editor: Safis Editing
Project Coordinator: Manthan Patel
Proofreader: Safis Editing
Indexer: Hemangini Bari
Production Designer: Joshua Misquitta
Marketing Coordinator: Anamika Singh
First published: November 2022
Production reference: 1171122
Published by Packt Publishing Ltd.
Livery Place
35 Livery Street
Birmingham
B3 2PB, UK.
ISBN 978-1-80461-829-5
www.packt.com
Dedicated to every web developer out there. Always keep learning, keep innovating, and keep sharing. Thanks for all your efforts and hard work!
– Florian Rappl
Florian Rappl is a solution architect working on distributed web applications for digital transformation and IoT projects. His main interest lies in the implementation of micro frontends and their impact on teams and business models. In this area, he has led several teams, realizing many successful projects over the last few years.
As the lead architect, he has helped to create outstanding web applications for many industry-leading companies. He regularly gives lectures on software design patterns and web development. Florian has won multiple prizes for his work over the years and is recognized as a Microsoft MVP for development technologies.
He started his career in software engineering before studying physics and helping to build an energy-efficient supercomputer. Florian currently lives in Munich, Germany, with his wife and their two daughters.
Israel Antonio Rosales Laguan is an experienced full stack software engineer using JavaScript, React, and Node.js with a focus on process improvement, developer ergonomics, systems integration, and pipeline automation. He also has strong experience in international SCRUM teams and mentoring others, working in Equinox, OnDeck, and Lazard, among others. Other expertise includes OWASP compliance, GraphQL, CI/CD with Docker, and advanced CSS.
Abhishek Kumar Maurya is a senior software engineer with 12+ years of industry experience. He currently works at Rippling, but has worked at various organizations such as Oracle India Pvt Ltd and founded his own start-up. He graduated from Banaras Hindu University, Varanasi, and post-graduated from NIT Trichy. He has worked on both the backend and frontend and is currently focusing on the frontend with Node.js and React.
This book covers everything necessary to make you use the power of Node.js, its concepts, and its ecosystem to the fullest. This includes everything you need to know about module systems, packages, helper libraries, CLI tools, WebAssembly, and a range of available tools such as bundlers (Webpack (v5), Parcel (v2), Vite, and esbuild), test runners (AVA, Jest, and Mocha), transpilers (Babel and TypeScript), and many more tools (Flow, Prettier, eslint, and Stylelint) are also covered.
This book is for junior and intermediate-level frontend web developers who are looking to leverage the Node.js ecosystem for building frontend solutions. The book requires beginner-level knowledge of JavaScript, HTML, and CSS. Prior experience in using a standard shell (sh) will be beneficial.
Chapter 1, Learning about the Internals of Node.js, describes the inner workings of Node.js, its principles, and basic ideas. This chapter also makes you familiar with the essential Node.js command-line tooling.
Chapter 2, Dividing Code into Modules and Packages, introduces different module formats, their advantages and disadvantages, and their support within Node.js. The chapter also introduces the important package.json file for defining Node.js packages.
Chapter 3, Choosing a Package Manager, describes and compares the different established command-line utilities for installing and managing third-party dependencies in your Node.js packages.
Chapter 4, Using Different Flavors of JavaScript, covers the main concepts and ideas for using different flavors of JavaScript with Node.js. These flavors include Flow and TypeScript, but also more recent specifications of the ECMAScript standard than those supported by the currently available version of Node.js.
Chapter 5, Enhancing Code Quality with Linters and Formatters, covers the available utilities for improving the code quality of JavaScript projects. This chapter has information on how to install these code quality helpers, configure them, and have them integrated into standard workflows and development processes.
Chapter 6, Building Web Apps with Bundlers, discusses everything you need to know about dedicated web build tools known as bundlers. In this chapter, you will learn how state-of-the-art web projects are compiled from source code to artifacts that can be published on a server. The covered bundlers include Webpack, esbuild, Parcel, and Vite.
Chapter 7, Improving Reliability with Testing Tools, covers everything you need to know about testing with Node.js – from tools for running unit tests to full end-to-end test runners. In particular, the chapter includes elementary knowledge about Jest, Mocha, AVA, Playwright, and Cypress.
Chapter 8, Publishing npm Packages, contains useful information to publish and consume packages from the official npm registry or a custom private registry such as Verdaccio. The chapter also covers the creation and publishing of CLI tools with Node.js, as well as information about writing isomorphic libraries.
Chapter 9, Structuring Code in Monorepos, covers general strategies for the development of multiple dependent packages with Node.js. In particular, it goes into the details of working on multiple packages within a single repository known as a monorepo. Possible tools, such as Nx, Lerna, or Turbo, are introduced in combination with npm, Yarn, and pnpm workspaces.
Chapter 10, Integrating Native Code with WebAssembly, discusses the possibility of running native code compiled to WebAssembly. The chapter guides you through creating your first WebAssembly module, as well as running the created module in the browser and in Node.js.
Chapter 11, Using Alternative Runtimes, offers a detailed view of two alternatives to Node.js: Deno and Bun. Both are evaluated in terms of compatibility, security, performance, and stability.
All the examples in the book have been created with simplicity in mind. They all work similarly and only require knowledge in core frontend technologies such as JavaScript with HTML and CSS. Additionally, some basic knowledge in using a terminal is necessary in order to follow all examples. The tooling to make the code run is discussed throughout the book. As such, if you know how to work with JavaScript, and follow the book explaining how to use Node.js with npm, you’ll have no problems running the examples presented in the book.
In Chapter 11, you’ll also run Deno and Bun. The chapter itself contains installation instructions.
If you are using the digital version of this book, we advise you to type the code yourself or access the code from the book’s GitHub repository (a link is available in the next section). Doing so will help you avoid any potential errors related to the copying and pasting of code.
You can download the example code files for this book from GitHub at https://github.com/PacktPublishing/Modern-Frontend-Development-with-Node.js. If there’s an update to the code, it will be updated in the GitHub repository.
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
The Code in Action videos for this book can be viewed at http://bit.ly/3EgcKwM.
We also provide a PDF file that has color images of the screenshots and diagrams used in this book. You can download it here: https://packt.link/zqKz4.
There are a number of text conventions used throughout this book.
Code in text: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: “Mount the downloaded WebStorm-10*.dmg disk image file as another disk in your system.”
A block of code is set as follows:
html, body, #map { height: 100%; margin: 0; padding: 0 }When we wish to draw your attention to a particular part of a code block, the relevant lines or items are set in bold:
[default] exten => s,1,Dial(Zap/1|30) exten => s,2,Voicemail(u100) exten => s,102,Voicemail(b100) exten => i,1,Voicemail(s0)Any command-line input or output is written as follows:
$ npm installBold: Indicates a new term, an important word, or words that you see onscreen. For instance, words in menus or dialog boxes appear in bold. Here is an example: “Select System info from the Administration panel.”
Tips or important notes
Appear like this.
Feedback from our readers is always welcome.
General feedback: If you have questions about any aspect of this book, email us at [email protected] and mention the book title in the subject of your message.
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/support/errata and fill in the form.
Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.
Once you’ve read Modern Frontend Development with Node.js, we’d love to hear your thoughts! Please click here to go straight to the Amazon review page for this book and share your feedback.
Your review is important to us and the tech community and will help us make sure we’re delivering excellent quality content.
Thanks for purchasing this book!
Do you like to read on the go but are unable to carry your print books everywhere?
Is your eBook purchase not compatible with the device of your choice?
Don’t worry, now with every Packt book you get a DRM-free PDF version of that book at no cost.
Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical books directly into your application.
The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free content in your inbox daily!
Follow these simple steps to get the benefits:
Scan the QR code or visit the link below:https://packt.link/free-ebook/9781804618295
Submit your proof of purchaseThat’s it! We’ll send your free PDF and other benefits to your email directlyIn this part, you’ll dive into Node.js by learning how it works and how you can use it. You’ll also get in touch with the Node.js ecosystem. In particular, you’ll see how Node.js projects are structured. An important topic of this part is how to deal with dependencies in the form of packages.
This part of the book comprises the following chapters:
Chapter 1, Learning about the Internals of Node.jsChapter 2, Dividing Code into Modules and PackagesChapter 3, Choosing a Package ManagerFor years, being a frontend developer meant writing a bit of HTML and putting some styling with CSS on it. However, since the last decade, this job description barely holds true. In contrast, the majority of frontend work is now done using JavaScript.
Initially used to make cosmetic enhancements to websites (such as the toggling of elements) possible, frontend development is now the glue of the web. Websites are no longer just written in HTML and CSS. Instead, in many cases, web pages are programmed with JavaScript using modern techniques such as dependency management and bundling of resources. The Node.js framework provides an ideal foundation for this movement. It enables developers to use JavaScript not only inside websites running in a browser but also within the tooling to write web pages – outside of a browser.
When Node.js was released in May 2009, it did not seem like a big deal. JavaScript was working on the server too. However, the cross-platform nature of Node.js and the size of the JavaScript community provided the basis for one of the greatest disruptions in the history of computing. People started adopting the framework so quickly that many existing frameworks either disappeared or had to be reworked to stay attractive to developers. Soon, JavaScript was used in the browser and on the server and was also part of every frontend developer’s toolbox.
With the rise of new development frameworks such as Angular or React, the need for attractive frontend tooling became apparent. The new frameworks always relied on some build steps – otherwise, websites and applications using these frameworks would have been far too inconvenient to write for developers. Since the vast Node.js ecosystem seemed to have figured out a suitable approach for reusability, these new frameworks adopted it and made it an integral part of their development story. This way, using Node.js became the de facto standard for frontend projects of any kind.
Today, it is pretty much impossible to start a frontend development project without having Node.js installed. In this book, we’ll take the journey of learning about Node.js from the inside out together. We will not be focusing on writing server applications or walking over the integrated functionality of Node.js. Instead, we’ll look at how we – as frontend developers – can leverage the best that Node.js brings to the table.
In this first chapter, we discuss the internals of Node.js. This will help you understand how Node.js works and how you can actually use it. After this chapter, you will be able to run and debug simple scripts using the Node.js command-line application.
We will cover the following key topics in this chapter:
Looking at the Node.js architecture in detailUnderstanding the event loopUsing Node.js from the command lineCommonJSTo follow the code samples in this book, you need knowledge of JavaScript and how to use the command line. You should have Node.js installed using the instructions at https://nodejs.org.
The complete source code for this chapter is available at https://github.com/PacktPublishing/Modern-Frontend-Development-with-Node.js/tree/main/Chapter01.
The Code in Action (CiA) videos for this chapter can be accessed at http://bit.ly/3fPPdtb.
The principal foundations of Node.js have been inspired by a few things:
The single worker thread featured in browsers was already quite successful in the server space. Here, the popular nginx web server showed that the event loop pattern (explained later in this chapter) was actually a blessing for performance – eliminating the need to use a dedicated thread pool for handling requests.The idea of packaging everything in a file-centric structure called modules. This allowed Node.js to avoid many of the pitfalls of other languages and frameworks – including JavaScript in the browser.The idea of avoiding creating a huge framework and leaving everything extensible and easy to get via package managers.Threads
Modern computers offer a lot of computing power. However, for an application to really use the available computing power, we need to have multiple things working in parallel. Modern operating systems know about different independently running tasks via so-called threads. A thread is a group of operations running sequentially, which means in a given order. The operating system then schedules when threads run and where (i.e., on which CPU core) they are placed.
These principles together form a platform that seems easy to create, but hard to replicate. After all, there are plenty of JavaScript engines and useful libraries available. For Ryan Dahl, the original creator and maintainer of Node.js, the basis of the framework had to be rock solid.
Ryan Dahl selected an existing JavaScript engine (V8) to take over the responsibility of parsing and running the code written in JavaScript. The V8 engine was chosen for two good reasons. On the one hand, the engine was available as an open source project under a permissive license – usable by projects such as Node.js. On the other hand, V8 was also the engine used by Google for its web browser Chrome. It is very fast, very reliable, and under active development.
One of the drawbacks of using V8 is that it was written in C++ using custom-built tooling called GYP. While GYP was replaced in V8 years later, the transition was not so easy for Node.js. As of today, Node.js is still relying on GYP as a build system. The fact that V8 is written in C++ seems like a side note at first, but might be pretty important if you ever intend to write so-called native modules.
Native modules allow you to go beyond JavaScript and Node.js – making full use of the available hardware and system capabilities. One drawback of native modules is that they must be built on each platform. This is against the cross-platform nature of Node.js.
Let’s take a step back to arrange the parts mentioned so far in an architecture diagram. Figure 1.1 shows how Node.js is composed internally:
Figure 1.1 – Internal composition of Node.js
The most important component in Node.js’s architecture – besides the JavaScript engine – is the libuv library. libuv is a multi-platform, low-level library that provides support for asynchronous input/output (I/O) based on an event loop. I/O happens in multiple forms, such as writing files or handling HTTP requests. In general, I/O refers to anything that is handled in a dedicated area of the operating system.
Any application running Node.js is written in JavaScript or some flavor of it. When Node.js starts running the application, the JavaScript is parsed and evaluated by V8. All the standard objects, such as console, expose some bindings that are part of the Node.js API. These low-level functions (such as console.log or fetch) make use of libuv. Therefore, some simple script that only works against language features such as primitive calculations (2 + 3) does not require anything from the Node API and will remain independent of libuv. In contrast, once a low-level function (for example, a function to access the network) is used, libuv can be the workforce behind it.
In Figure 1.2, a block diagram illustrating the various API layers is shown. The beauty of this diagram is that it reveals what Node.js actually is: a JavaScript runtime allowing access to low-level functionality from state-of-the-art C/C++ libraries. The Node.js API consists of the included Node.js bindings and some C/C++ addons:
Figure 1.2 – Composition of Node.js in terms of building blocks
One thing that would need explanation in the preceding diagram is how the event loop is implemented in relation to all the blocks. When talking about Node.js’s internal architecture, a broader discussion of what an event loop is and why it matters for Node.js is definitely required. So let’s get into these details.
An event loop is a runtime model that enables users to run all operations from a single thread – irrespective of whether the operations access long-running external resources or not. For this to work, the event loop needs to make requests to an event provider, which calls the specified event handlers. In Node.js, the libuv library is used for event loop implementation.
The reason for giving libuv the most space in Figure 1.1 is to highlight the importance of this library. Internally, libuv is used for everything regarding I/O, which arguably is the most crucial piece of any framework. I/O lets a framework communicate with other resources, such as files, servers, or databases. By default, dealing with I/O is done in a blocking manner. This means that the sequence of operations in our application is essentially stopped, waiting for the I/O operation to finish.
Two strategies for mitigating the performance implications of blocking I/O exist.
The first strategy is to create new threads for actually performing these blocking I/O operations. Since a thread contains an independent group of operations, it can run concurrently, eventually not stopping the operations running in the original thread of the application.
The second strategy is to not use blocking I/O at all. Instead, use an alternative variant, which is usually called non-blocking I/O or asynchronous I/O. Non-blocking I/O works with callbacks, that is, functions that are called under certain conditions – for instance when the I/O operation is finished. Node.js uses libuv to make extensive use of this second strategy. This allows Node.js to run all code in a single thread, while I/O operations run concurrently.
In Figure 1.3, the building blocks of libuv are displayed. The key part is that libuv already comes with a lot of functionality to handle network I/O. Furthermore, file and DNS operations are also covered well:
Figure 1.3 – Building blocks of libuv
In addition to the different I/O operations, the library comes with a set of different options for handling asynchronous user code.
The event loop itself follows the reactor design pattern. Wikipedia describes the pattern as follows:
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers. (https://en.wikipedia.org/wiki/Reactor_pattern)
Importantly, this definition mentions synchronous dispatch. This means that code that is run through the event loop is guaranteed to not run into any conflicts. The event loop makes sure that code is always run sequentially. Even though the I/O operations may concurrently run, our callbacks will never be invoked in parallel. From our perspective, even though Node.js will internally (through libuv) use multiple threads, the whole application is single-threaded.
The following is a simple script that shows you the basic behavior of the event loop at play – we’ll discuss how to run this in the Using Node.js from the command line section:
events.js
console.log('A [start]'); setTimeout(() => console.log('B [timeout]'), 0); Promise.resolve().then(() => console.log('C [promise]')); console.log('D [end]');We will run this script in the next section when we learn about the command line usage of Node.js. In the meantime, put some thought into the preceding code and write down the order in which you’ll see the console output. Do you think it will print in an “ABCD” order, or something else?
The algorithm of the implementation of the event loop in libuv is displayed in Figure 1.4:
Figure 1.4 – The implementation of the event loop in libuv
While the code snippet only deals with JavaScript-related constructs (such as console, Promise, and setTimeout), in general, the callbacks are associated with resources that go beyond Node.js, such as file system changes or network requests. Some of these resources may have an operating system equivalent; others only exist in form of blocking I/O.
Consequently, the event loop implementation always considers its thread pool and polls for progressed I/O operations. Timers (such as setTimeout in the example script) are only run in the beginning. To know whether a timer needs to be run, its due time is compared with the current time. The current time is synced with the system time initially. If there is nothing to be done anymore (that is, no active timer, no resource waiting to finish, etc.), then the loop exits.
Let’s see how we can run Node.js to solidify our knowledge about the event loop.
Using JavaScript for a web application just requires you to open the website in a browser. The browser will evaluate the included JavaScript and run it. When you want to use JavaScript as a scripting language, you need to find a new way of running JavaScript. Node.js offers this new way – running JavaScript in a terminal, inside our computer, or from a server.
When Node.js is installed, it comes with a set of command-line tools that will be available in the terminal of your choice. For this book, you’ll need to know about three different executables that we’ll use throughout the chapters:
node: The main application to run a Node.js scriptnpm: The default package manager – more on that laternpx: A very convenient utility to run npm binariesFor now, we only need to know about node. If we want to run the events.js script from the previous section, we need to execute the following command in the directory in which the script (events.js) has been placed. You can place it there by just inserting the content from the previous events.js listing:
$ node events.js A [start] D [end] C [promise] B [timeout]The command is shown after the conventional $ sign indicating the command prompt. The output of running the script is shown below the node events.js command.
As you can see, the order is “ADCB” – that is, Node.js first handled all the sequential operations before the callbacks of the promise were handled. Finally, the timeout callback was handled.
The reason for handling the promise callback before the timeout callback lies in the event loop. In JavaScript, promises spawn so-called micro tasks, which are placed in the pending callback section of the libuv event loop from Figure 1.4. The timeout callback, however, is treated like a full task. The difference between them lies within the event loop. Micro tasks are placed in an optimized queue that is actually peeked multiple times per event loop iteration.
According to libuv, the timeout callback can only be run when its timer is due. Since we only placed it in the event loop during the idle handles (i.e., main section) of the event loop, we need to wait until the next iteration of the event loop.
The node command-line application can also receive additional parameters. The official documentation goes into all details (https://nodejs.org/api/cli.html). A helpful one is -e (short version of --eval) to just evaluate a script directly from the command-line input without requiring a file to run:
$ node -e "console.log(new Date())" 2022-04-29T09:20:44.401Another very helpful command line flag is --inspect
