In our daily work, sometimes we need an HTTP service to access our static resources to test the situation, and sometimes we need to find a file in a huge folder, layer by layer is still quite troublesome. Of course, there are many similar solutions in the market, such as building NGINX local services to access static resources, including HTTP-server, etc

Address demand pain points

  • Implement a static resource server
  • Enable services in any folder and visual interface to access internal resources

Building engineering

In making, for example

Remember to check the following three options, it will automatically initialize several configuration files, save yourself typing

Pull the project locally and start configuring ESLint and Husky

Code specification

With the development of the front-end more specification, the size of the project will be more and more large, developers involved more and more, a project collaboration scenarios also more and more, the code of the specification is a big problem, the emphasis on oral is not enough, need to have a set of specifications to constraint, save labor costs, efficiency is higher.

EditorConfig + Prettier + ESlint

  • Solve the problem of poor readability and maintainability caused by non-standard code between teams
  • Resolve code specification inconsistency caused by different editors used by team members
  • Send out code style problems in advance, give corresponding hints, timely repair
  • Reduce the number of iterations in the code review process and save time
  • Automatic formatting, uniform code style
Integrate the EditorConfig configuration

EditorConfig helps maintain a consistent code style for multiple developers working on the same project on different IDE editors.

Website: editorconfig.org

Create the.editorConfig file at the root of the project:

# Editor configuration, see http://editorconfig.org# indicates the top-level EditorConfig configuration file root =true[*] # indicates that all files apply charset = utf-8Set character set to UTF -8Indent_style = space # indented style (TAB | space) indent_size =2# # end_of_line indentation size = lf control line type (lf | cr | CRLF) trim_trailing_whitespace =trueInsert_final_newline =true# Always insert a new line at the end of the file [*.md] # indicates that only md files apply the following rule max_line_length = off TRIM_trailing_whitespace =false
Copy the code

Note:

  • VSCode to use EditorConfig requires going to the plugin market to download the pluginEditorConfig VS Code

  • JetBrains (WebStorm, IntelliJ IDEA) can be configured directly using EditorConfig without additional plug-ins.
Integrating the Prettier configuration

Prettier Prettier is a powerful code formatting tool that supports JavaScript, Typescript, Css, Scss, Less, JSX, Angular, Vue, GraphQL, JSON, Markdown, etc. Prettier is a powerful code formatting tool that supports JavaScript, Typescript, Css, Scss, JSX, Vue, GraphQL, JSON, Markdown, etc. Is currently the most popular formatting tool.

Liverpoolfc.tv: prettier. IO /

  1. Install the Prettier

    npm i prettier -D

  2. Example Create the Prettier configuration file

    Prettier supports configuration files in various formats, such as. Json,. Yml, yaml, and. Js. Create the.prettierrc file in the root directory

  3. Configuration. The prettierrc

In this project, we do the following simple configuration. For more information about configuration items, please refer to the official website

{
  "useTabs": false."tabWidth": 2."printWidth": 100."singleQuote": true."trailingComma": "none"."bracketSpacing": true."semi": false
}
Copy the code
  1. After Prettier is installed and configured, she can format code using commands
# Format all files (. Means all files) NPX prettier --writeCopy the code

Note:

  • The VSCode editor Prettier requires downloading the plug-in Prettier-code Formatter to use Prettier for configuration

  • JetBrains (WebStorm, IntelliJ IDEA) can be configured directly using EditorConfig without additional plug-ins.

Prettier when editing an editor such as VSCode or WebStorm, Prettier is formatted according to the format of the configuration file Prettier, avoiding code styles where editing tools differ from Prettier.

Integrate ESlint configuration

ESLint is a tool for finding and reporting problems in code, and for auto-fixing some problems. Its core is the ability to analyze code to check code quality and style problems through pattern matching of AST (Abstract Syntax Tree) obtained from code parsing.

As we mentioned before as team members programming ability and coding habits between different quality problems caused by the code, we use ESLint to solve, while writing code to find problems, if found wrong, prompt is given rules, and automatically repair, go down for a long time, can make team members gravitate to the same kind of coding style.

  1. Install ESLint

    The installation can be global or local, and the authors recommend local installation (only in the current project).

    npm i eslint -D

  2. Configuration ESLint

    After ESLint is installed successfully, execute NPX ESLint –init and follow terminal instructions to complete a series of Settings to create the configuration file.

    The plug-in
    Airbnb JavaScript Style Guide
    JavaScript Standard Style
    Google JavaScript Style Guide

Operation:

  • How would you like to use ESLint? (How would you like to use ESLint?)

Here we select To check syntax, find problems, and Enforce code style.

  • What type of modules does your project use? (What type of modules do you use for your project?)

We’re going to select JavaScript modules (import/export)

  • Which framework does your project use? (Which framework do you use for your project?)

We choose vue.js here

  • Does your project use TypeScript? (Does your project use TypeScript?)

So let’s say Yes

  • Where does your code run? (Where does your code run?)

Here we select Browser and Node (press the space bar to select, then press Enter to confirm)

  • How would you like to define a style for your project? (How would you like to define style for your project?)

Use a popular style guide

  • Which style guide do you want to follow? (Which style guide would you like to follow?)

Here we choose Airbnb: github.com/airbnb/java…

ESLint lists three community-popular JavaScript style guides for us: Airbnb, Standard, and Google.

All three style guides are good enough to be used by companies large and small around the world, based on years of development experience. We chose the Airbnb with the most stars on GitHub to avoid the hassle of configuring ESLint rules, and then let the team learn the Airbnb JavaScript style guide.

At this point, we’ve configured Airbnb JavaScript rules in ESLint so that any code that doesn’t conform to Airbnb’s style will be flagged by the editor and fixed automatically when coding.

The authors do not recommend that you configure ESLint rules freely, and trust me, these three JavaScript code style guides are worth learning over and over again to improve your programming skills.

  • The authors do not recommend that you configure ESLint rules freely, and trust me, these three JavaScript code style guides are worth learning over and over again to improve your programming skills.

Let’s choose JavaScript here

  • Would you like to install them now with npm? (Would you like to install them with NPM now?)

Using the above option, ESLint will automatically look for missing dependencies, so here we select Yes and use NPM to download and install these dependency packages.

Note: If the automatic installation dependency fails, manual installation is required

npm i @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-airbnb-base eslint-plugin-import eslint-plugin-vue -D
Copy the code
  1. ESlint configuration files.eslintrc.js

After the previous step, the.eslintrc.js configuration file is automatically generated in the project root directory:

module.exports = {
  env: {
    browser: true.es2021: true.node: true,},extends: ["plugin:vue/essential"."airbnb-base"].parserOptions: {
    ecmaVersion: 12.parser: "@typescript-eslint/parser".sourceType: "module",},plugins: ["vue"."@typescript-eslint"].rules: {}};Copy the code

Depending on the project, if we have additional ESLint rules, we will append them to this file.

Note:

  • To use the ESLint configuration file for VSCode, you need to download the plugin ESLint from the plugin market.

  • JetBrains (WebStorm, IntelliJ IDEA, etc.) does not require additional plugins.

After the configuration is complete, we enable ESLin in an editor such as VSCode or WebStorm. When writing code, ESLint will check the code in real time according to the rules we configured, and will give error messages and fix solutions if any problems are found.

As shown in figure:

  • VScode

  • WebStorm

Although, now the editor has given error prompts and fixes, but we need to click one by one to fix, or quite troublesome. As simple as that, we simply set the editor to automatically execute the esLint –fix command for code style fixes when saving files.

  • VSCode adds the following code to settings.json:
"editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
 }
Copy the code
  • WebStorm opens the Settings window, do as follows, and finally hit Apply -> OK.

8. Resolve conflict between Prettier and ESLint

When you add extra configuration rules for ESLint and Prettier to your project, there are often rules that conflict.

The ESLint configuration in this project uses the Airbnb JavaScript style guide validation. One of the rules is to put a semicolon after the end of code, while we added an unsemicolon after the end of code in the Prettier configuration file, so there’s a conflict, Prettier formatting code occurs after Prettier is used, ESLint detects formatting errors and throws an error. Eslint-plugin-prettier and eslint-config-prettier are used to solve conflicts between the two.

  • eslint-plugin-prettierPrettier’s rule into ESLint’s rule
  • eslint-config-prettierDisable the rule in ESLint that conflicts with Prettier.

Prettier Configuration Rule > ESLint Configates rules.

  • Installing a plug-in
npm i eslint-plugin-prettier eslint-config-prettier -D
Copy the code
  • Add the prettier plugin to.eslintrc.js
module.exports = {
  ...
  extends: [
    'plugin:vue/essential'.'airbnb-base'.'plugin:prettier/recommended' // Add the prettier plug-in],... }Copy the code

When esLint –fix was executed, ESLint would format code according to Prettier’s configuration, easily resolving the conflict between the two.

Integrate Husky and Lint-staged

We already integrate ESLint and Prettier into the project, where they verify code in real time and, to a certain extent, regulate it, but some people on the team may find the restrictions cumbersome and choose to ignore “hints”. Write your code in your own style, or disable the tools altogether, and commit your code directly to the repository. Over time, ESLint becomes useless.

So, we also need to make certain that the repository code is spec compliant by making it impossible to commit code that has not passed ESLint detection and fixes.

To solve this problem, you need to use a Git Hook. When a local Git commit is performed, ESLint checks and fixes the committed code (i.e., ESLint –fix). If the code fails to pass the ESLint rule, the commit is disabled.

To achieve this feature, we resorted to Husky + Lint-staged.

Husky — Git Hook, which can be set to trigger Git commands at various stages (pre-commit, commit-msg, pre-push, etc.).

Lint-staged — Run linters on files temporarily stored in Git.

Configuration husky
  • Automatic configuration (recommended)

Use the husky-init command to quickly initialize a husky configuration in your project.

npx husky-init && npm install

This command does four things:

  1. The installationhuskyTo development dependencies

2. Create the project in the root directory.huskydirectory

In 3..huskyThe directory to createpre-commit hookAnd initializepre-commitCommand tonpm test

4. Modifypackage.jsonScripts, added"prepare": "husky install"

Here, husky is configured and now we can use it:

Husky contains many hooks, such as pre-commit, commit-msg, and pre-push. Here, we use pre-commit to trigger the ESLint command.

Alter.husky/pre-commit hook trigger command:

eslint --fix ./src --ext .vue,.js,.ts
Copy the code

The above pre-commit hook file does the following: Git commit -m “XXX” : $git commit -m “XXX” : $git commit -m “XXX” : $git commit -m

However, there is a problem: sometimes we need to implement ESLint –fix for all files when we have changed only one or two files. If this were a history project and we configured ESLint rules halfway through, then other unmodified “history” files would also be checked when submitting code, which could result in a large number of ESLint errors, obviously not the desired result.

We want to use ESLint only to fix the code we wrote this time and not affect the rest of the code. So there’s a magical tool called Lint-Staged.

Configuration lint – staged

Lint-staged this tool, commonly used in conjunction with Husky, allows Husky hook triggered commands to only act on Git add files (files in git staging) without affecting other files.

Next, we continued optimizing the project using Lint-staged.

  1. Install the lint – staged
npm i lint-staged -D
Copy the code
  1. inpackage.jsonIncrease in thelint-stagedConfiguration items

"lint-staged": {
  "*.{vue,js,ts}": "eslint --fix"
},
Copy the code

Run eslint –fix only for.vue,.js,.ts files in git staging.

  1. Modify the.husky/pre-commitThe trigger command of hook is:npx lint-staged

Husky and Lint-staged configurations are now complete.

Now when we submit code, it looks like this:

Add./ SRC/git commit -m “test…” Eslint –fix will be executed for only the two files test-1.js and test-2.ts. If ESLint passes, the commit succeeds, otherwise the commit terminates. This ensures that the code we submit to the Git repository is canonical.

  • Before submissiontest-1.js,test-2.ts

  • After submissiontest-1.js,test-2.tsAutomatically fix the code format

No matter writing code or doing other things, you should take a long-term view. At the beginning of using ESint, there may be a lot of problems, and it is very time-consuming and laborious to change. As long as you stick to it, the code quality and development efficiency will be improved, and the early efforts are worth it.

These tools are not necessary, and you can develop features without them, but you can write higher-quality code with these tools. In particular, some people who are new to these tools may feel troublesome and give up using them, missing a good opportunity to improve their programming skills.

Breakpoint debugging

Click on theCreate the launch.json file

Vscode /launch.json will be generated in the root directory

The following two debugging modes are introduced:

  1. attach
"scripts": {
    ...
    "debug": "node --inspect-brk ./src/app.js",
    ...
}
Copy the code

Perform yarn debug

Chrome devtools

Fill in port 9229:

You can then see that Chrome has scanned the target, and click Inspect to connect to the Debugger Server.

attach

{"name": "Attach", "port": 9229, // replace inspect-brk service port" request": "Attach", "skipFiles": [ "<node_internals>/**" ], "type": "pwa-node" },Copy the code

launch

Start the debugger server via node –inspect-brk and then add vscode debug to connect to the server

There are ways to do this, just add a launch configuration:

Program can modify the debug path, and you can set stopOnEntry to stop on the first line

More debugging details can be found at mp.weixin.qq.com/s/-Hz4SkAp9…

At this point, our basic configuration is complete and we can start the formal coding

Create an HTTP server

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) = > {
  res.statusCode = 200;
  res.setHeader('Content-Type'.'text/plain');
  res.end('Hello, World! \n');
});

server.listen(port, hostname, () = > {
  console.log(`Server running at http://${hostname}:${port}/ `);
});
Copy the code

One Hello World

Of course, this is just a demo, but in order to achieve our goal, we need to deal with req, RES, which is the request parameters, and RES, which is the response parameters

Unified encapsulates a method to deal with

route(req, res, filePath, config)
Copy the code
const fs = require('fs')
// Asynchronous processing
const { promisify } = require('util')
// Template engine
const handlebars = require('handlebars')
// read the file stream in segments
const range = require('./range')
// mimeType
const mime = require('./mime')
const path = require('path')

// Asynchronous processing
const stat = promisify(fs.stat)
const readdir = promisify(fs.readdir)
// Splice the template path
const tplPath = path.join(__dirname, '.. /template/dir.tpl')
// Template file stream
const source = fs.readFileSync(tplPath)
// Handlebars engine processing
const template = handlebars.compile(source.toString())

// req Request body res Response content filePath Absolute path of the current file, config to obtain a root path
module.exports = async function (req, res, filePath, config) {
  try {
    // Read files asynchronously
    const stats = await stat(filePath)
    // Check whether it is a file
    if (stats.isFile()) {
      // Dynamically read the extension of the current file and get the corresponding mimeType
      const contentType = mime(filePath)
      // Set the response header
      res.setHeader('Content-Type', contentType)
      // Read the file stream
      let rs
      const { code, start, end } = range(stats.size, req, res)
      if (code === 200) {
        res.statusCode = 200
        rs = fs.createReadStream(filePath)
      } else {
        res.statusCode = 206
        rs = fs.createReadStream(filePath, { start, end })
      }
      rs.pipe(res)
    } else if (stats.isDirectory()) {
      // Read the list of files under the folder
      const files = await readdir(filePath)
      res.statusCode = 200
      res.setHeader('Content-Type'.'text/html')
      // relative(path.relative(from, to)
      // 
      const dir = path.relative(config.root, filePath)
      // Data passed to the template file
      const data = {
        title: path.basename(filePath),
        dir: dir ? ` /${dir}` : ' '.files: files.map((file) = > {
          return {
            file,
            icon: mime(file)
          }
        })
      }
      res.end(template(data))
    }
  } catch (error) {
    res.statusCode = 404
    res.setHeader('Content-Type'.'text/plain')
    res.end(`${filePath} is not a diretory or file \n ${error.toString()}`)
    console.error(error)
  }
}
Copy the code

1. Handle promisify asynchronously

const stat = promisify(fs.stat) 
const readdir = promisify(fs.readdir)

const stats = await stat(filePath)
Copy the code

2. Template engines

const handlebars = require('handlebars')

// Splice the template path
const tplPath = path.join(__dirname, '.. /template/dir.tpl') 
// Template file stream
const source = fs.readFileSync(tplPath) 
// Handlebars engine processing
const template = handlebars.compile(source.toString())

// Data passed to the template file
const data = { 
    title: path.basename(filePath), 
    dir: dir ? ` /${dir}` : ' '.files: files.map((file) = > { return { file, icon: mime(file) } }) 
} 
res.end(template(data))
Copy the code

When the server responds to a resource to the front end, a content-Type is specified and the browser parses the rendered Content in the corresponding format

Let ext = path.extName (filePath).split(‘.’).pop().tolowerCase (), path.extName gets the extension and maps a mimeType value to content-Type

4. Range Reads resources in segments

module.exports = (totalSize, req, res) = > {
  const { range } = req.headers
  if(! range) {return {
      code: 200}}const sizes = range.match(/bytes=(\d*)-(\d*)/)
  const end = sizes[2] || totalSize - 1
  const start = sizes[1] || totalSize - end
  if (start > end || start < 0 || end > totalSize) {
    return {
      code: 200
    }
  }

  res.setHeader('Accept-Ranges'.'bytes')
  res.setHeader('Content-Range'.`bytes ${start}-${end}/${totalSize}`)
  res.setHeader('Content-Length', end - start)

  return {
    code: 206.start: global.parseInt(start),
    end: global.parseInt(end)
  }
}
Copy the code
let rs
const { code, start, end } = range(stats.size, req, res)
if (code === 200) {
    res.statusCode = 200
    rs = fs.createReadStream(filePath)
} else {
    res.statusCode = 206
    rs = fs.createReadStream(filePath, { start, end })
}
Copy the code

5, resource compression (mainly aimed at two forms: gizp | deflate)

const { createGzip, createDeflate } = require('zlib')

module.exports = (rs, req, res) = > {
  const acceptEncoding = req.headers['accept-encoding']
  if(! acceptEncoding || ! acceptEncoding.match(/\b(gizp|deflate)\b/)) {
    return rs
  }
  if (acceptEncoding.match(/\bgizp\b/)) {
    res.setHeader('Content-Encoding'.'gzip')
    return rs.pipe(createGzip())
  }
  if (acceptEncoding.match(/\bdeflate\b/)) {
    res.setHeader('Content-Encoding'.'deflate')
    return rs.pipe(createDeflate())
  }
  return false
}
Copy the code
rs = compress(rs, req, res)
Copy the code

6, caching,

const { cache } = require('.. /config/defaultConfig')

function refreshRes(stats, res) {
  const { maxAge, expires, cacheControl, lastModified } = cache
  if (expires) {
    res.setHeader('Expires'.new Date(Date.now() + maxAge * 1000).toUTCString())
  }
  if (cacheControl) {
    res.setHeader('Cache-Control'.`public, max-age=${maxAge}`)}if (lastModified) {
    res.setHeader('Last-Modified', stats.mtime.toUTCString())
  }
  // if (etag) {
  // res.setHeader('Etag', `${stats.size}-${stats.mtime}`)
  // }
}
module.exports = function isFresh(stats, req, res) {
  refreshRes(stats, res)

  const lastModified = req.headers['if-modified-since']
  const etag = req.headers['if-none-match']

  if(! lastModified && ! etag) {return false
  }
  if(lastModified && lastModified ! == res.getHeader('Last-Modified')) {
    return false
  }
  // if (etag && etag ! == res.getHeader('Etag')) {
  // return false
  // }
  return true
}
Copy the code

chmod

Bin /index -p port number bin/index -p port number bin/index can be executed if an error is reported.

-rwxr-xr-x

First representative: D (folder) – (file)

RWX corresponds to read (write) and execute (execute) permissions

Learn more about chmod: www.runoob.com/linux/linux…

Release NPM

Create bin/dserver from the root directory

#! /usr/bin/env node

require('.. /src/index')
Copy the code

package.json

"bin": {
    "dserver": "bin/dserver" // The "dserver" key is the command example for using the NPM package dserver -p 9999
},
Copy the code

Since app.js is automatic, it needs to be wrapped. The class encapsulates app.js

The parameters of processing yargs start projects, in particular, using reference as follows, more detailed document reference: www.npmjs.com/package/yar…

const yargs = require('yargs')
const Server = require('./app')

const { argv } = yargs
  .usage('anywhere [options]')
  .option('p', { alias: 'port'.describe: 'Port number'.default: 9527 })
  .option('h', {
    alias: 'hostname'.describe: 'host'.default: '127.0.0.1'
  })
  .option('d', {
    alias: 'root'.describe: 'root path'.default: process.cwd()
  })
  .version()
  .alias('v'.'version')
  .help()

console.log(argv)
const server = new Server(argv)
server.start()
Copy the code

Once configured, NPM can be published and NPM publish is OK

If your release doesn't work, check out the following possibilities. Don't lose heart when you run into difficulties. Check out this checklist: 1. Check whether you have logged in to NPM (NPM whoami) 2. Check whether the email address you filled in when registering is verified. If the page is not refreshed, click the notification bar at the top to resend the verification email 4. Check if your package name is already in use 5. Check if your version number is already in use 6. Check if your NPM source is an official NPM source (NPM config list)Copy the code

Write in the last

Making: github.com/Spring-List…