“This is the first day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021”

preface

Front-end engineering, in short, is software engineering + front-end, presented in the form of automation. Front-end engineering, from the development phase to the code release production environment, includes the following:

  • The development of
  • build
  • test
  • The deployment of
  • performance
  • specification

Below, we choose several representative aspects for in-depth study of front-end engineering according to the above contents.

Review: [Youth Camp] – Understand front-end engineering

The scaffold

What is scaffolding? (What)

The most popular front-end scaffolding is written based on NodeJs, such as vue-CLI, create-react-app, and DVA-CLI.

What is the purpose of scaffolding? (according to)

As the concept of front-end engineering becomes more popular, scaffolding is a command line tool introduced to reduce repetitive work. Get rid of CTRL + C, CTRL + V. Now a new front-end project is no longer a simple matter of introducing CSS in the HTML header and JS in the tail. CSS is written in Sass or Less, introduced in JS, and then dynamically built into HTML. In addition to learning basic JS, CSS syntax and popular framework, we also need to learn how to configure webpack, Babel and other building tools, how to start front-end services, how to hot update; In order for the editor to check errors during writing and be more formal, we also need to introduce ESlint; Some projects even need to introduce unit testing (Jest). For a more beginner, this would undoubtedly be daunting. The appearance of front-end scaffolding makes things simple, one key command, a new project, and then execute two NPM commands, run a project. When you get started, you don’t have to worry about configuration, you just have fun writing code.

How to implement a new project scaffolding (koA based)? (How)

Let’s sort out the implementation idea

The core idea of our scaffolding implementation is automation thinking, which will be repetitive CTRL + C, CTRL + V to create projects, with the program to solve. The solution steps are as follows:

  1. Create folder (Project name)
  2. Create index. Js
  3. Create package. Json
  4. Install dependencies

1. Create a folder

Before creating a folder, you need to delete it:

// package.json
{
...
    "scripts": {
        "test": "rm -rf ./haha && node --experimental-modules index.js"
    }
...
}
Copy the code

Create folders: We use the mkdirSync API to create folders by introducing the NODEJS FS module.

// index.js import fs from 'fs'; function getRootPath() { return "./haha"; } // generate folder fs.mkdirsync (getRootPath());Copy the code

2. Create index. Js

Create index.js file using nodejs fs module writeFileSync API:

// index.js
fs.writeFileSync(getRootPath() + "/index.js", createIndexTemplate(inputConfig));
Copy the code

Now let’s see, how do we generate dynamic templates? The ideal approach is to dynamically generate file templates through configuration, so let’s look at the logic implemented by createIndexTemplate.

// index.js import fs from 'fs'; import { createIndexTemplate } from "./indexTemplate.js"; // input // process // output const inputConfig = { middleWare: { router: true, static: true } } function getRootPath() { return "./haha"; } // generate folder fs.mkdirsync (getRootPath()); Js file fs.writefilesync (getRootPath() + "/index.js", createIndexTemplate(inputConfig));Copy the code
// indexTemplate.js import ejs from "ejs"; import fs from "fs"; import prettier from "prettier"; Export function createIndexTemplate(config) {export function createIndexTemplate(config) {// Read template const template = fs.readFileSync("./template/index.ejs", "utf-8"); / / const ejs rendering code = ejs. Render (template, {router: config. Middleware. The router, the static: config. The middleware, the static, the port: config.port, }); Format (code, {parser: "Babel ",}); // Return prettier. }Copy the code
// template/index.ejs
const Koa = require("koa");
<% if (router) { %>
  const Router = require("koa-router");
<% } %>


<% if (static) { %>
const serve = require("koa-static");
<% } %>

const app = new Koa();

<% if (router) { %>
const router = new Router();
router.get("/", (ctx) => {
  ctx.body = "hello koa-setup-heihei";
});
app.use(router.routes());
<% } %>

<% if (static) { %>
app.use(serve(__dirname + "/static"));
<% } %>

app.listen(<%= port %>, () => {
  console.log("open server localhost:<%= port %>");
});
Copy the code

3. Create a package. Json

Create package. Json file, essence is and the creation of the index, js, is the thinking of using dynamically generated template to do this, we’ll look at core method createPackageJsonTemplate implementation code:

// packageJsonTemplate.js
function createPackageJsonTemplate(config) {
  const template = fs.readFileSync("./template/package.ejs", "utf-8");

  const code = ejs.render(template, {
    packageName: config.packageName,
    router: config.middleware.router,
    static: config.middleware.static,
  });

  return prettier.format(code, {
    parser: "json",
  });
}
Copy the code
/ / template/package. Ejs {" name ":" < % = packageName % > ", "version" : "1.0.0", "description" : ""," main ": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "" and" license ", "ISC", "dependencies" : {" koa ":" tokens ^ 2.13.1 "< % if (router) {% >," koa - the router ": "^" 10.1.1} < % % > < % if (static) {% >, "koa - static" : "^ 5.0.0"}} < % % >}Copy the code

4. Install dependencies

To automatically install dependencies, we can use the Nodejs execa library to execute the YARN install command:

execa("yarn", {
  cwd: getRootPath(),
  stdio: [2, 2, 2],
});
Copy the code

At this point, we have implemented the scaffolding for our new project using NodeJS. Finally, we can sort out the optimizable points and upgrade it. For example, program configuration is upgraded to GUI user configuration (the user passes in configuration parameters, such as project name, by manual selection or input).

Compile build

What is a compile build?

Build, or compile, is the most cumbersome and complex module in the front-end engineering system. It is responsible for converting source code into executable code for the host browser, and its core is resource management. Front-end output resources include JS, CSS, HTML, etc., and the corresponding source code is:

  • JS code written ahead of the browser implementation of the ECMAScript specification (ES6/7/8…) .
  • LESS/SASS precompiled syntax for CSS code.
  • HTML code with Jade/EJS/Mustache and other template syntax.

The above source code will not run in a browser environment, and the core of the build work is to convert it into host-executable code, corresponding to:

  • Translation of the ECMAScript specification.
  • CSS precompiled syntax translation.
  • HTML template rendering.

So let’s take a look at the three major build tools today: Webpack, Rollup, and Vite.

Webpack

Webpack principle

To really use the Webpack build tool, we need to understand how it works. The Webpack compilation project works by recursively finding all dependent modules, converting the source code into browser executable code, and building the output bundle. The specific workflow steps are as follows:

  1. Initialization parameters: Take configuration file and shell script parameters and merge them
  2. Start compiling: Initialize with the parameters obtained in the previous stepcompilerObject, executerunMethod starts compiling
  3. Determine the entry: according to the configurationentry, determine the entry file
  4. Compiling modules: Starting from the entry file, recursively traverse to find all files that depend on modules
  5. Complete module compilation: useloaderTranslate all modules to get the final content and dependencies after translation
  6. Output resources: Assembled individually according to entry and module dependencieschunkAdd to the output list
  7. Output complete: according to the configurationoutput, determine the output path and file name, and write the contents of the file to the output directory (default isdist)

Webpack practice

1. Basic configuration

entry

Entry configuration, Webpack compilation build can find the compiled entry file, and then build the internal dependency map.

output

Output configuration that tells WebPack where to export the bundles it creates and how to name those files.

loader

Module converter, loader can process the browser cannot run directly file modules, conversion into valid modules. For example, CSS-loader and style-loader handle styles; Url-loader and file-loader process images.

plugin

Plugins, which solve the problem that loader cannot implement, can be extended throughout the webPack build life cycle. For example: package optimization, resource management, injection of environment variables, etc.

Here is a simple example of a basic WebPack configuration:

const path = require("path"); module.exports = { mode: "development", entry: "./src/index.js", output: { filename: "main.js", path: Path. resolve(__dirname, "dist"),}, devServer: {static: "./dist",}, module: {rules: [{ / \. CSS $/, / / use the loader, from the back to the former executive use: [" style - loader ", "CSS - loader"],}],,}};Copy the code

Reference webpack website: webpack.docschina.org/concepts/ (note: use a different version of the webpack switch corresponding version of the document)

2. Performance optimization

Compilation speed optimization

Checking compilation speed

Look for tools that measure compile speed, such as speed-measure-webpack-plugin, to analyze the execution time of each loader and plugin.

How to optimize compilation speed?

  1. Reduce the time spent searching for dependencies
  • Configure test, include, and Exclue of the Loader to narrow the search scope and shorten the search time
  1. Reduce the time spent parsing transformations
  • NoParse precisely filters modules that do not need to be parsed
  • If the Loader consumes too much performance, enable multiple processes
  1. Reduce the time to build the output
  • Compress the code, open multi – process
  1. Use caching policies wisely
  • Babel-loader enables caching
  • Caching is enabled for intermediate modules, such as the hard-source-webpack-plugin

Specific optimization measures can refer to: Webpack performance optimization experience | project review

Volume optimization

Check the package size

Look for tools that detect the size of the package after it is built, such as the Webpack-Bundle-Analyzer plug-in, which analyzes the size of each module of the bundle generated after it is packed.

So how do you optimize volume?

  1. The bundle removes third-party dependencies
  2. Remove useless code Tree Shaking

Specific optimization measures reference: Webpack performance optimization experience | project review

Rollup

A Rollup overview

Rollup is a JavaScript module wrapper that compiles small pieces of code into large, complex pieces of code, such as libraries or applications. And you can use new standardized formats for code modules, such as CommonJS and ES Module.

A Rollup principle

Let’s take a look at the Rollup principle. Its main working mechanism is as follows:

  1. Identify entry file
  2. useAcornRead the parse file to get the abstract syntax tree AST
  3. Analysis of the code
  4. Generate code, output

Rollup packages are lighter and better suited for library packaging than Webpack because of the built-in Tree Shaking mechanism that lets you know which files are being brought in and not called at the code analysis stage and automatically erase unused code as you package.

Acorn is a JavaScript parser that parses JavaScript strings into a syntax abstract tree called AST. If you want to know the AST syntax tree, you can check out astexplorer.net/

The Rollup practice

input

Entry file path

output

Output files, output formats (AMD/ES6 / IIFE/UMD/CJS), SourcemAP enabled, etc.

plugin

Configurations used by various plug-ins

external

Extracting external dependencies

global

Configuring global variables

Here is a simple example of Rollup’s basic configuration:

import commonjs from "@rollup/plugin-commonjs"; import resolve from "@rollup/plugin-node-resolve"; Import {terser} from 'rollup-plugin-terser'; import {terser} from 'rollup-plugin-terser'; export default { input: "src/main.js", output: [{ file: "dist/esmbundle.js", format: "esm", plugins: [terser()] },{ file: "dist/cjsbundle.js", format: "CJS ",}], // Commonjs needs to be placed before the transform plugin, // However, there is another exception, which is that it needs to be placed after Babel: [json(), resolve(), commonjs()], external: ["vue"] };Copy the code

Vite

Vite overview

Vite, compared to Webpack, Rollup and other tools, greatly improve the development experience of front-end developers, compilation speed is extremely fast.

Vite principle

Why Vite development compiles so fast? Let’s explore how it works first.As can be seen from the figure above, the principle of Vite is to use modern mainstream browsers to support the native ESM specification, with server to do interception, the code compiled into the browser support.

Vite practical experience

We can build a Hello World version of the Vite project to experience the rapid development experience:

Note: Vite requires Node.js version >= 12.0.0.

Use NPM:

$ npm init vite@latest
Copy the code

Using Yarn:

$ yarn create vite
Copy the code

Above is the compilation time of Vite project, 363ms, developing second compilation experience, really awesome!

Comprehensive comparison of three construction tools

Webpack Rollup Vite
Compilation speed general faster The fastest
HMR hot update support Additional plug-ins need to be introduced support
Tree Shaking Additional configuration required support support
Scope of application Project package The class library package Projects that do not consider compatibility

test

As our front-end projects get bigger and bigger, development iterations become more expensive to maintain, with dozens of modules calling each other intricately, and tests need to be written to improve code quality and maintainability. Here are three types of tests that front-end engineering often does.

Unit testing

Unit testing is the examination and verification of the smallest testable unit (typically a single function, class, or component). There are many frameworks for unit testing, such as Mocha, assertion library Chai, Sinon, Jest, etc. We can choose JEST first, because it integrates Mocha, Chai, Jsdom, Sinon, etc. Next, let’s see how Jest writes unit tests.

  1. Write tests for correctness, that is, correct input should have normal results.
  2. Write tests for error, meaning that bad input should be bad results.

Take the example of verifying the summation function:

Module. exports = (a,b) => {return a+b; }Copy the code
// const add = require('./add.js'); Test ('should 1+1 = 2', ()=> {// Set const a = 1; const b = 1; // When const r = add(a,b); // verify -> then expect(r).tobe (2); })Copy the code

// Test ('should 1+1 = 2', ()=> { const b = 2; // When const r = add(a,b) then expect(r).tobe (2); })Copy the code

Component test

Component testing, which is primarily testing a component’s functionality, is more difficult because many components involve DOM manipulation. Component testing can be done with a component testing framework, such as Cypress (which can do component testing as well as E2E testing). Let’s look at component testing first, okay?

Take vuE3 component testing as an example:

  1. Let’s build it firstvue3 + viteProject, write test components
  2. To installcypressThe environment
  3. incypress/componentWrite a component test script file
  4. performcypress open-ctCommand, startcypress component testingService running ofxx.spec.jsTest the script so that you can visually see the individual components automatically executing the operation logic
// button. vue module <template> <div> </template> <script> export default {} </script> <style> </style>Copy the code
// cypress/plugin/index.js config const {startDevServer} = require('@cypress/vite-dev-server') // eslint-disable-next-line  no-unused-vars module.exports = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config on('dev-server:start', (options) => { const viteConfig = { // import or inline your vite configuration from vite.config.js } return startDevServer({ options, viteConfig }) }) return config; }Copy the code
/ / cypress/component/Button. Spec. Js Button component test script import {mount} from "@ cypress/vue"; import Button from ".. /.. /src/components/Button.vue"; Describe ("Button", () => {it("should show Button", () => {// Mount (Button); cy.contains("Button"); }); });Copy the code

E2e test

E2e testing, also known as end-to-end testing, simulates a series of user actions on a page and verifies that it meets expectations. We can also use Cypress for E2E testing. How do we do that?

Take todo List as an example:

  1. Let’s build it firstvue3 + viteProject, write test components
  2. To installcypressThe environment
  3. incypress/integrationWrite a component test script file
  4. performcypress openCommand, startcypressService, choosexx.spec.jsTest scripts, you can see the operation process of simulated users
/ / cypress/integration/todo. Spec. Js todo functional test script the describe (' example to-do app ', () => { beforeEach(() => { cy.visit('https://example.cypress.io/todo') }) it('displays two todo items by default', () => { cy.get('.todo-list li').first().should('have.text', 'Pay electric bill') cy.get('.todo-list li').last().should('have.text', 'Walk the dog') }) it('can add new todo items', () => { const newItem = 'Feed the cat' cy.get('[data-test=new-todo]').type(`${newItem}{enter}`) cy.get('.todo-list li') .should('have.length', 3) .last() .should('have.text', newItem) }) it('can check off an item as completed', () => { cy.contains('Pay electric bill') .parent() .find('input[type=checkbox]') .check() cy.contains('Pay electric bill') .parents('li') .should('have.class', 'completed') }) context('with a checked task', () => { beforeEach(() => { cy.contains('Pay electric bill') .parent() .find('input[type=checkbox]') .check() }) it('can filter for uncompleted tasks', () => { cy.contains('Active').click() cy.get('.todo-list li') .should('have.length', 1) .first() .should('have.text', 'Walk the dog') cy.contains('Pay electric bill').should('not.exist') }) it('can filter for completed tasks', () => { // We can perform similar steps as the test above to ensure // that only completed tasks are shown cy.contains('Completed').click() cy.get('.todo-list li') .should('have.length', 1) .first() .should('have.text', 'Pay electric bill') cy.contains('Walk the dog').should('not.exist') }) it('can delete all completed tasks', () => { cy.contains('Clear completed').click() cy.get('.todo-list li') .should('have.length', 1) .should('not.have.text', 'Pay electric bill') cy.contains('Clear completed').should('not.exist') }) }) })Copy the code

conclusion

In the foreword part of this paper, six aspects of development, construction, performance, testing, deployment and specification are introduced comprehensively. The main body of this paper mainly introduces the core technologies of front-end engineering applied in practice projects. I hope this article can help those who are learning front-end engineering to build a complete knowledge graph