From 91aca1754e40dfb3ec6ba7b693bc8423721db577 Mon Sep 17 00:00:00 2001 From: gdut-yy Date: Sat, 29 Aug 2020 23:16:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=AB=98=E4=BA=AE=20ch17.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ch17.md | 229 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 136 insertions(+), 93 deletions(-) diff --git a/docs/ch17.md b/docs/ch17.md index bf9d434..41fa102 100644 --- a/docs/ch17.md +++ b/docs/ch17.md @@ -3,21 +3,14 @@ Congratulations on reaching the final chapter of this book. If you have read eve The tools and language extensions covered in this chapter are: -ESLint for finding potential bugs and style problems in your code. - -Prettier for formatting your JavaScript code in a standardized way. - -Jest as an all-in-one solution for writing JavaScript unit tests. - -npm for managing and installing the software libraries that your program depends on. - -Code-bundling tools—like webpack, Rollup, and Parcel—that convert your modules of JavaScript code into a single bundle for use on the web. - -Babel for translating JavaScript code that uses brand-new language features (or that uses language extensions) into JavaScript code that can run in current web browsers. - -The JSX language extension (used by the React framework) that allows you to describe user interfaces using JavaScript expressions that look like HTML markup. - -The Flow language extension (or the similar TypeScript extension) that allows you to annotate your JavaScript code with types and check your code for type safety. +- ESLint for finding potential bugs and style problems in your code. +- Prettier for formatting your JavaScript code in a standardized way. +- Jest as an all-in-one solution for writing JavaScript unit tests. +- npm for managing and installing the software libraries that your program depends on. +- Code-bundling tools—like webpack, Rollup, and Parcel—that convert your modules of JavaScript code into a single bundle for use on the web. +- Babel for translating JavaScript code that uses brand-new language features (or that uses language extensions) into JavaScript code that can run in current web browsers. +- The JSX language extension (used by the React framework) that allows you to describe user interfaces using JavaScript expressions that look like HTML markup. +- The Flow language extension (or the similar TypeScript extension) that allows you to annotate your JavaScript code with types and check your code for type safety. This chapter does not document these tools and extensions in any comprehensive way. The goal is simply to explain them in enough depth that you can understand why they are useful and when you might want to use them. Everything covered in this chapter is widely used in the JavaScript programming world, and if you do decide to adopt a tool or extension, you’ll find lots of documentation and tutorials online. @@ -25,7 +18,7 @@ This chapter does not document these tools and extensions in any comprehensive w In programming, the term lint refers to code that, while technically correct, is unsightly, or a possible bug, or suboptimal in some way. A linter is a tool for detecting lint in your code, and linting is the process of running a linter on your code (and then fixing your code to remove the lint so that the linter no longer complains). The most commonly used linter for JavaScript today is ESLint. If you run it and then take the time to actually fix the issues it points out, it will make your code cleaner and less likely to have bugs. Consider the following code: - +```js var x = 'unused'; export function factorial(x) { @@ -35,8 +28,9 @@ export function factorial(x) { return x * factorial(x-1) } } +``` If you run ESLint on this code, you might get output like this: - +```js $ eslint code/ch17/linty.js code/ch17/linty.js @@ -49,6 +43,7 @@ code/ch17/linty.js ✖ 6 problems (5 errors, 1 warning) 3 errors and 1 warning potentially fixable with the `--fix` option. +``` Linters can seem nitpicky sometimes. Does it really matter whether we used double quotes or single quotes for our strings? On the other hand, getting indentation right is important for readability, and using === and let instead of == and var protects you from subtle bugs. And unused variables are dead weight in your code—there is no reason to keep those around. ESLint defines many linting rules and has an ecosystem of plug-ins that add many more. But ESLint is fully configurable, and you can define a configuration file that tunes ESLint to enforce exactly the rules you want and only those rules. @@ -59,14 +54,15 @@ One of the reasons that some projects use linters is to enforce a consistent cod A modern alternative to enforcing code formatting rules via a linter is to adopt a tool like Prettier to automatically parse and reformat all of your code. Suppose you have written the following function, which works, but is formatted unconventionally: - +```js function factorial(x) { if(x===1){return 1} else{return x*factorial(x-1)} } +``` Running Prettier on this code fixes the indentation, adds missing semicolons, adds spaces around binary operators and inserts line breaks after { and before }, resulting in much more conventional-looking code: - +```js $ prettier factorial.js function factorial(x) { if (x === 1) { @@ -75,6 +71,7 @@ function factorial(x) { return x * factorial(x - 1); } } +``` If you invoke Prettier with the --write option, it will simply reformat the specified file in place rather than printing a reformatted version. If you use git to manage your source code, you can invoke Prettier with the --write option in a commit hook so that code is automatically formatted before being checked in. Prettier is particularly powerful if you configure your code editor to run it automatically every time you save a file. I find it liberating to write sloppy code and see it fixed automatically for me. @@ -89,7 +86,7 @@ Writing tests is an important part of any nontrivial programming project. Dynami Suppose you’ve written the following function: const getJSON = require("./getJSON.js"); - +```js /** * getTemperature() takes the name of a city as its input, and returns * a Promise that will resolve to the current temperature of that city, @@ -139,8 +136,9 @@ describe("getTemperature()", () => { expect(await getTemperature("x")).toBe(212); // We expect 212F }); }); +``` With the test written, we can use the jest command to run it, and we discover that one of our tests fails: - +```js $ jest getTemperature FAIL ch17/getTemperature.test.js getTemperature() @@ -169,8 +167,9 @@ Tests: 1 failed, 1 passed, 2 total Snapshots: 0 total Time: 1.403s Ran all test suites matching /getTemperature/i. +``` Our getTemperature() implementation is using the wrong formula for converting C to F. It multiplies by 5 and divides by 9 rather than multiplying by 9 and dividing by 5. If we fix the code and run Jest again, we can see the tests pass. And, as a bonus, if we add the --coverage argument when we invoke jest, it will compute and display the code coverage for our tests: - +```js $ jest --coverage getTemperature PASS ch17/getTemperature.test.js getTemperature() @@ -189,6 +188,7 @@ Tests: 2 passed, 2 total Snapshots: 0 total Time: 1.508s Ran all test suites matching /getTemperature/i. +``` Running our test gave us 100% code coverage for the module we were testing, which is exactly what we wanted. It only gave us partial coverage of getJSON(), but we mocked that module and were not trying to test it, so that is expected. ## 17.4 Package Management with npm @@ -198,16 +198,18 @@ npm is the package manager that is bundled with Node, and was introduced in §16 If you are trying out someone else’s JavaScript project, then one of the first things you will often do after downloading their code is to type npm install. This reads the dependencies listed in the package.json file and downloads the third-party packages that the project needs and saves them in a node_modules/ directory. -You can also type npm install to install a particular package to your project’s node_modules/ directory: - +You can also type npm install `` to install a particular package to your project’s node_modules/ directory: +```js $ npm install express +``` In addition to installing the named package, npm also makes a record of the dependency in the package.json file for the project. Recording dependencies in this way is what allows others to install those dependencies simply by typing npm install. The other kind of dependency is on developer tools that are needed by developers who want to work on your project, but aren’t actually needed to run the code. If a project uses Prettier, for example, to ensure that all of its code is consistently formatted, then Prettier is a “dev dependency,” and you can install and record one of these with --save-dev: - +```js $ npm install --save-dev prettier +``` Sometimes you might want to install developer tools globally so that they are accessible anywhere even for code that is not part of a formal project with a package.json file and a node_modules/ directory. For that you can use the -g (for global) option: - +```js $ npm install -g eslint jest /usr/local/bin/eslint -> /usr/local/lib/node_modules/eslint/bin/eslint.js /usr/local/bin/jest -> /usr/local/lib/node_modules/jest/bin/jest.js @@ -219,14 +221,16 @@ $ which eslint /usr/local/bin/eslint $ which jest /usr/local/bin/jest +``` In addition to the “install” command, npm supports “uninstall” and “update” commands, which do what their names say. npm also has an interesting “audit” command that you can use to find and fix security vulnerabilities in your dependencies: - +```js $ npm audit --fix === npm audit security report === found 0 vulnerabilities in 876354 scanned packages +``` When you install a tool like ESLint locally for a project, the eslint script winds up in ./node_modules/.bin/eslint, which makes the command awkward to run. Fortunately, npm is bundled with a command known as “npx,” which you can use to run locally installed tools with commands like npx eslint or npx jest. (And if you use npx to invoke a tool that has not been installed yet, it will install it for you.) The company behind npm also maintains the https://npmjs.com package repository, which holds hundreds of thousands of open source packages. But you don’t have to use the npm package manager to access this repository of packages. Alternatives include yarn and pnpm. @@ -236,26 +240,20 @@ If you are writing a large JavaScript program to run in web browsers, you will p ES6 modules are nearly universally supported by web browsers today, but web developers still tend to use code bundlers, at least when releasing production code. Developers find that user experience is best when a single medium-sized bundle of code is loaded when a user first visits a website than when many small modules are loaded. -NOTE +#### NOTE Web performance is a notoriously tricky topic and there are lots of variables to consider, including ongoing improvements by browser vendors, so the only way to be sure of the fastest way to load your code is by testing thoroughly and measuring carefully. Keep in mind that there is one variable that is completely under your control: code size. Less JavaScript code will always load and run faster than more JavaScript code! There are a number of good JavaScript bundler tools available. Commonly used bundlers include webpack, Rollup and Parcel. The basic features of bundlers are more or less the same, and they are differentiated based on how configurable they are or how easy they are to use. Webpack has been around for a long time, has a large ecosystem of plug-ins, is highly configurable, and can support older nonmodule libraries. But it can also be complex and hard to configure. At the other end of the spectrum is Parcel which is intended as a zero-configuration alternative that simply does the right thing. In addition to performing basic bundling, bundler tools can also provide some additional features: -Some programs have more than one entry point. A web application with multiple pages, for example, could be written with a different entry point for each page. Bundlers generally allow you to create one bundle per entry point or to create a single bundle that supports multiple entry points. - -Programs can use import() in its functional form (§10.3.6) instead of its static form to dynamically load modules when they are actually needed rather than statically loading them at program startup time. Doing this is often a good way to improve the startup time for your program. Bundler tools that support import() may be able to produce multiple output bundles: one to load at startup time, and one or more that are loaded dynamically when needed. This can work well if there are only a few calls to import() in your program and they load modules with relatively disjoint sets of dependencies. If the dynamically loaded modules share dependencies then it becomes tricky to figure out how many bundles to produce, and you are likely to have to manually configure your bundler to sort this out. - -Bundlers can generally output a source map file that defines a mapping between the lines of code in the bundle and the corresponding lines in the original source files. This allows browser developer tools to automatically display JavaScript errors at their original unbundled locations. - -Sometimes when you import a module into your program, you only use a few of its features. A good bundler tool can analyze the code to determine which parts are unused and can be omitted from the bundles. This feature goes by the whimsical name of “tree-shaking.” - -Bundlers typically have a plug-in–based architecture and support plug-ins that allow importing and bundling “modules” that are not actually files of JavaScript code. Suppose that your program includes a large JSON-compatible data structure. Code bundlers can be configured to allow you to move that data structure into a separate JSON file and then import it into your program with a declaration like import widgets from "./big-widget-list.json". Similarly, web developers who embed CSS into their JavaScript programs can use bundler plug-ins that allow them to import CSS files with an import directive. Note, however, that if you import anything other than a JavaScript file, you are using a nonstandard JavaScript extension and making your code dependent on the bundler tool. - -In a language like JavaScript that does not require compilation, running a bundler tool feels like a compilation step, and it is frustrating to have to run a bundler after every code edit before you can run the code in your browser. Bundlers typically support filesystem watchers that detect edits to any files in a project directory and automatically regenerate the necessary bundles. With this feature in place you can typically save your code and then immediately reload your web browser window to try it out. - -Some bundlers also support a “hot module replacement” mode for developers where each time a bundle is regenerated, it is automatically loaded into the browser. When this works, it is a magical experience for developers, but there are some tricks going on under the hood to make it work, and it is not suitable for all projects. +- Some programs have more than one entry point. A web application with multiple pages, for example, could be written with a different entry point for each page. Bundlers generally allow you to create one bundle per entry point or to create a single bundle that supports multiple entry points. +- Programs can use import() in its functional form (§10.3.6) instead of its static form to dynamically load modules when they are actually needed rather than statically loading them at program startup time. Doing this is often a good way to improve the startup time for your program. Bundler tools that support import() may be able to produce multiple output bundles: one to load at startup time, and one or more that are loaded dynamically when needed. This can work well if there are only a few calls to import() in your program and they load modules with relatively disjoint sets of dependencies. If the dynamically loaded modules share dependencies then it becomes tricky to figure out how many bundles to produce, and you are likely to have to manually configure your bundler to sort this out. +- Bundlers can generally output a source map file that defines a mapping between the lines of code in the bundle and the corresponding lines in the original source files. This allows browser developer tools to automatically display JavaScript errors at their original unbundled locations. +- Sometimes when you import a module into your program, you only use a few of its features. A good bundler tool can analyze the code to determine which parts are unused and can be omitted from the bundles. This feature goes by the whimsical name of “tree-shaking.” +- Bundlers typically have a plug-in–based architecture and support plug-ins that allow importing and bundling “modules” that are not actually files of JavaScript code. Suppose that your program includes a large JSON-compatible data structure. Code bundlers can be configured to allow you to move that data structure into a separate JSON file and then import it into your program with a declaration like import widgets from "./big-widget-list.json". Similarly, web developers who embed CSS into their JavaScript programs can use bundler plug-ins that allow them to import CSS files with an import directive. Note, however, that if you import anything other than a JavaScript file, you are using a nonstandard JavaScript extension and making your code dependent on the bundler tool. +- In a language like JavaScript that does not require compilation, running a bundler tool feels like a compilation step, and it is frustrating to have to run a bundler after every code edit before you can run the code in your browser. Bundlers typically support filesystem watchers that detect edits to any files in a project directory and automatically regenerate the necessary bundles. With this feature in place you can typically save your code and then immediately reload your web browser window to try it out. +- Some bundlers also support a “hot module replacement” mode for developers where each time a bundle is regenerated, it is automatically loaded into the browser. When this works, it is a magical experience for developers, but there are some tricks going on under the hood to make it work, and it is not suitable for all projects. ## 17.6 Transpilation with Babel Babel is a tool that compiles JavaScript written using modern language features into JavaScript that does not use those modern language features. Because it compiles JavaScript to JavaScript, Babel is sometimes called a “transpiler.” Babel was created so that web developers could use the new language features of ES6 and later while still targeting web browsers that only supported ES5. @@ -274,23 +272,27 @@ Even though there is less need to transform the core JavaScript language today, JSX is an extension to core JavaScript that uses HTML-style syntax to define a tree of elements. JSX is most closely associated with the React framework for user interfaces on the web. In React, the trees of elements defined with JSX are ultimately rendered into a web browser as HTML. Even if you have no plans to use React yourself, its popularity means that you are likely to see code that uses JSX. This section explains what you need to know to make sense of of it. (This section is about the JSX language extension, not about React, and it explains only enough of React to provide context for the JSX syntax.) You can think of a JSX element as a new type of JavaScript expression syntax. JavaScript string literals are delimited with quotation marks, and regular expression literals are delimited with slashes. In the same way, JSX expression literals are delimited with angle brackets. Here is a very simple one: - +```js let line =
; +``` If you use JSX, you will need to use Babel (or a similar tool) to compile JSX expressions into regular JavaScript. The transformation is simple enough that some developers choose to use React without using JSX. Babel transforms the JSX expression in this assignment statement into a simple function call: - +```js let line = React.createElement("hr", null); +``` JSX syntax is HTML-like, and like HTML elements, React elements can have attributes like these: - +```js let image = ; +``` When an element has one or more attributes, they become properties of an object passed as the second argument to createElement(): - +```js let image = React.createElement("img", { src: "logo.png", alt: "The JSX logo", hidden: true }); +``` Like HTML elements, JSX elements can have strings and other elements as children. Just as JavaScript’s arithmetic operators can be used to write arithmetic expressions of arbitrary complexity, JSX elements can also be nested arbitrarily deeply to create trees of elements: - +```js let sidebar = (

Title

@@ -298,8 +300,9 @@ let sidebar = (

This is the sidebar content

); +``` Regular JavaScript function call expressions can also be nested arbitrarily deeply, and these nested JSX expressions translate into a set of nested createElement() calls. When an JSX element has children, those children (which are typically strings and other JSX elements) are passed as the third and subsequent arguments: - +```js let sidebar = React.createElement( "div", { className: "sidebar"}, // This outer call creates a
React.createElement("h1", null, // This is the first child of the
@@ -307,10 +310,11 @@ let sidebar = React.createElement( React.createElement("hr", null), // The second child of the
. React.createElement("p", null, // And the third child. "This is the sidebar content")); +``` The value returned by React.createElement() is an ordinary JavaScript object that is used by React to render output in a browser window. Since this section is about the JSX syntax and not about React, we’re not going to go into any detail about the returned Element objects or the rendering process. It is worth noting that you can configure Babel to compile JSX elements to invocations of a different function, so if you think that JSX syntax would be a useful way to express other kinds of nested data structures, you can adopt it for your own non-React uses. An important feature of JSX syntax is that you can embed regular JavaScript expressions within JSX expressions. Within a JSX expression, text within curly braces is interpreted as plain JavaScript. These nested expressions are allowed as attribute values and as child elements. For example: - +```js function sidebar(className, title, content, drawLine=true) { return (
@@ -320,18 +324,20 @@ function sidebar(className, title, content, drawLine=true) {
); } +``` The sidebar() function returns a JSX element. It takes four arguments that it uses within the JSX element. The curly brace syntax may remind you of template literals that use ${} to include JavaScript expressions within strings. Since we know that JSX expressions compile into function invocations, it should not be surprising that arbitrary JavaScript expressions can be included because function invocations can be written with arbitrary expressions as well. This example code is translated by Babel into the following: - +```js function sidebar(className, title, content, drawLine=true) { return React.createElement("div", { className: className }, React.createElement("h1", null, title), drawLine && React.createElement("hr", null), React.createElement("p", null, content)); } -This code is easy to read and understand: the curly braces are gone and the resulting code passes the incoming function parameters to React.createElement() in a natural way. Note the neat trick that we’ve done here with the drawLine parameter and the short-circuiting && operator. If you call sidebar() with only three arguments, then drawLine defaults to true, and the fourth argument to the outer createElement() call is the
element. But if you pass false as the fourth argument to sidebar(), then the fourth argument to the outer createElement() call evaluates to false, and no
element is ever created. This use of the && operator is a common idiom in JSX to conditionally include or exclude a child element depending on the value of some other expression. (This idiom works with React because React simply ignores children that are false or null and does not produce any output for them.) +``` +This code is easy to read and understand: the curly braces are gone and the resulting code passes the incoming function parameters to React.createElement() in a natural way. Note the neat trick that we’ve done here with the drawLine parameter and the short-circuiting && operator. If you call sidebar() with only three arguments, then drawLine defaults to true, and the fourth argument to the outer createElement() call is the `
` element. But if you pass false as the fourth argument to sidebar(), then the fourth argument to the outer createElement() call evaluates to false, and no `
` element is ever created. This use of the && operator is a common idiom in JSX to conditionally include or exclude a child element depending on the value of some other expression. (This idiom works with React because React simply ignores children that are false or null and does not produce any output for them.) When you use JavaScript expressions within JSX expressions, you are not limited to simple values like the string and boolean values in the preceding example. Any JavaScript value is allowed. In fact, it is quite common in React programming to use objects, arrays, and functions. Consider the following function, for example: - +```js // Given an array of strings and a callback function return a JSX element // representing an HTML
    list with an array of
  • elements as its child. function list(items, callback) { @@ -343,8 +349,9 @@ function list(items, callback) {
); } -This function uses an object literal as the value of the style attribute on the