ESLint

ESLint 是一个静态代码分析工具(static program analysis,在没有任何程序指定的情况下,对代码进行分析)。

ESLint 可以帮助开发者在项目中建立统一的团队代码规范,确保正确、统一的代码风格,提高代码可读性、可维护性。

使用 ESLint

安装 eslint 依赖:

  1. npm i eslint -D

生成 eslint 配置文件(.eslintrc.js):

  1. npx eslint --init

生成的 eslint 配置文件大体如下(根据不同选项生成的内容不同):

// .eslintrc.js
module.exports = {
  env: {
    browser: true, // 运行环境是浏览器
    es2021: true, // 书写代码是 es2021 的
  },
  extends: [
    'airbnb-base', // 继承 airbnb-base 代码风格
  ],
  parser: '@typescript-eslint/parser', // 指定编译器, 默认是 espree, ts 代码是 @typescript-eslint/parser
  parserOptions: {
    ecmaVersion: 12, // ECMAScript 版本, 与上面 es2021 对应
    sourceType: 'module',
  },
  plugins: [
    '@typescript-eslint', // eslint 插件
  ],
  rules: {
    // 自定义规则, rules 的规则会覆盖 extends 的规则
    // off/0 关闭检测, wran/1 警告提示, error/2 报错编译失败
    // [key]: ('off' | 0) | ('wran' | 1) | ('error' | 2),
  },
};

配置完后就可以通过下面的命令对代码进行检测:

npx eslint 文件路径

VSCode 插件

每次都通过命令来做代码检测太过麻烦,可以安装一个 VSCode 插件来帮助我们在编写代码的过程中就对错误的语法进行提示,这个插件就是 ESLint:
image.png
ESLint 这个插件会帮助我们在代码开发的过程中就对错误的语法进行提示,但是需要我们手动进行修复这些错误的语法,如果我们想要自动修复这些错误语法的话,可以安装一个代码格式化工具 Prettier:
image.png Prettier 插件还可以创建一个 .prettierrc 文件,来对格式化的效果做 配置

// .prettierrc
{
    "printWidth": 100, // 代码超过多少宽度换行
  "singleQuote": true, // true 使用单引号格式化, false 使用双引号格式化
}

ESLint-loader

我们可以使用 eslint-loader 让 webpack 来对代码进行代码检测,而不必使用 eslint 命令行了。

npm i eslint-loader -D

修改 webpack 配置:

// webpack.config.js
module.exports = {
  module: {
      rules: [
      {
          test: /\.js$/,
        use: [
          'babel-loader',
          'eslint-loader',
        ],
      },
    ],
  },
};

适配 Vue

webpack 中想要使用 Vue 的 .vue 文件只需要下载 vue-loader 和 vue-template-compiler 即可:

npm i vue-loader vue-template-compiler -D

修改 webpack 配置文件:

// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader');

module.exports = {
    module: {
      rules: [
      {
          test: /\.vue$/,
        use: [
          {
              loader: 'vue-loader',
          },
        ],
      },
      // .vue 文件中的 style 标签, 直接配置对应 loader 即可
      {
                test: /\.css$/,
        use: [
            'style-loader',
          {
              loader: 'css-loader',
            options: {
                importLoaders: 2,
            },
          },
          'postcss-loader',
        ],
            },
    ],
  },
  plugins: [
      new VueLoaderPlugin(), // 注意:必须使用这个插件
  ],
}

搭建本地服务器

在运行代码之前需要先进行两步操作:

  - 操作一:运行 `npm run build` 打包代码
  - 操作二:打开 index.html 文件

每次修改代码后都要重复执行上面的操作十分影响开发效率,我们希望做到文件发生变化时可以自动打包,展示,为了实现这种效果,webpack 提供了几种方式: watch 、devServe。

watch

webpack 提供了 watch 模式,在该模式下,webpack 依赖图中所有的文件只要一个发生了变化,就会重新打包编译,就不需要手动运行 npm run build 了。

开启 watch 模式:

// 方法一: 在 package.json 添加 watch 脚本
{
    "scripts": {
    "build": "webpack --watch"
    }
}

// 方法二: 在 webpack.config 配置文件中开启
module.exports = {
    watch: true,
}

watch 可以自动打包编译,但是却不能让浏览器自动刷新,以及它会进行文件操作(打包出 build 文件夹),效率也不是很好。

DevServer

DevServer 也可以在文件发生变化时自动打包编译,同时也可以让自动刷新浏览器看到最新的效果,DevServer 在本地开了一个服务,同时编译后不会输出任何文件,而是将打包后的文件保存在了内存中减去了文件操作,提高了性能。

下载依赖:

npm i webpack-dev-server -D

配置 scripts 脚本:

// package.json
{
    "scripts": {
        "serve": "webpack serve" // 增加 serve
    }
}

配置好后直接运行 npm run serve 就可以了,devServe 服务默认开启的端口时 8080。

模块热替换 (HMR)

HMR(Hot Module Replacement)模块热替换指的是在应用程序运行的过程中,替换、添加、删除模块,而无需刷新整个页面。

使用 DevServer 时如果修改了文件内容会刷新整个页面来看到最新的效果,但是我们只修改了一小部分却导致了整个页面的刷新,这样的开发效率是很低的,我们希望只更新变化的部分,节省开发时间,这时就要使用 HMR。

webpack-dev-server 已经支持了 HMR ,直接开启就可以了:

// webpack.config.js
module.exports = {
    devServer: {
      hot: true, // 开启 HMR
  },
}

开启了配置后我们还需要指定哪些模块发生更新时,进行 HMR:

// 判断是否开启了 HMR
if (module.hot) {
  // 指定对应模块进行 HMR
    module.hot.accept('./util.js', () => {
    // 发生更新的回调, 可以省略
  });
}

browserslist 导致 HMR 失效问题

   坑点:使用了browserslist (配置文件、package.json 中定义)会导致 HMR 失效,这时可以配置 target = web 来解决这个问题:
// webpack.config.js
module.exports = {
    target: 'web',
};

React、Vue 的 HMR

Vue 的 HMR 只要使用 vue-loader 就可以了,React 的 HMR 需要下载对应插件来实现。

安装 React HMR 所需依赖:

npm install @pmmmwh/react-refresh-webpack-plugin react-refresh -D

修改 webpack 配置:

// webpack.config.js
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = {
    plugins: [
        new ReactRefreshPlugin(),  
  ],
}

修改 babel 配置,引入对应插件:

// babel.config.js
module.exports = {
    plugins: [
      ['react-refresh/babel']
  ]
}

注:在打包的时候不能使用 react-refresh-webpack-plugin 这个插件,这个插件只能配合 devServe 使用,不然会打包失败。

HMR 的原理

webpack-dev-server 会创建两个服务:1. 提供静态资源的服务(使用的时 express 搭建),2. Socket 服务(net.Scoket)。

express 服务负责提供静态资源的服务(打包后的资源被浏览器请求和解析)。

HMR 是一个 socket 长连接,当服务器监听到对应的模块发生变化时,会生成两个文件 .json(manifest 文件)和 .js (update chunk 文件),通过 socket 连接将这两个文件主动发送给浏览器,浏览器拿到两个文件后,通过 HMR runtime 机制,加载这两个文件,并且对修改的模块进行更新。
image.png

CSS 文件的 HMR

style-loader 中自带 HMR 功能,所以直接使用 style-loader 就可以了。

PublicPath 属性配置

在 webpack 的 output 和 devServe 中都有一个 publicPath 的配置。

output 中的 PublicPath

指定 publickPath:

// webpack.config.js
module.exports = {
    output: {
      filename: 'main.js',
    path: './build',
    publicPath: '/', // 指定 publicPath 为 /
  }
}

这个 publicPath 的作用是指定 index.html 文件打包引用的基本路径,它默认值是一个字符串,打包后引入 bundle 时路径为 bundle.js,指定 publicPath: ‘/‘ 后路径为 /bundle.js
image.png image.png
浏览器会根据所在的域名 + 路径去请求资源,比如当前的域名是 http://localhost:8000,那么未指定的 publicPath 的完整路径为 http://localhost:8000/main.js (浏览器自动加上 /), 指定 publicPath 的也是这个路径,因为自己加了后浏览器就不会加了。

注:这个路径不能乱指定,不然就找不到对应的文件了。

使用示例一:
在 Vue-cli 中这个 publicPath 默认为 /,如果打包后直接打开 index.html 文件时会发现代码不能正常运行,这时因为这时去加载 main.js 文件时使用的是 file 协议,file 协议去找 /main.js 时,路径为:file:///C:/main.js ,因为 C 盘下面没有 main.js 所以报错,这时可以通过将 publicPath 改为 ./ 解决。

使用示例二:
如果在部署的时候不是部署在根目录,则也需要指定这个 publicPath,比如部署在 /app 目录下,则 publicPath 也需要指定为 /app (不过一般都会用 Nginx 部署,所以一般不用配置)。

devServer 中的 publicPath

devServer 中也有一个 publicPath 属性,这个属性的功能是指定 devServer 启动的本地服务所在的目录文件,它的的默认值是 / , 也就是直接访问端口即可 http://localhost:8080 , 如果将其设置为 /app, 那么这时访问时需要 http://localhost:8080/app 才可以。

注意: 如果设置了 devServer 的 publicPath 时,output 中的 publicPath 也要设置为相同值,不然访问不到打包后的文件。

// webpack.config.js
module.exports = {
  output: {
      publicPath: '/app',
  },
  devServer: {
    publicPath: '/app',
  },
}

devServer 的其他配置

hotOnly

如果代码中出错修复后,默认会重新刷新整个页面,配置 hotOnly 后不会重新刷新页面。

module.exports = {
    devServer: {
      hot: true,
    hotOnly: true,
  },
}

host

配置 host,默认值为 localhost。

module.exports = {
    devServer: {
      host: '0.0.0.0',
  },
}

port

配置监听端口,默认为 8080:

module.exports = {
    devServer: {
    port: 8000,
  },
}

open

运行打包命令后,自动开启浏览器。

module.exports = {
    devServer: {
    open: true,
  },
}

compress

开启静态文件 Gizp 压缩(只是在开发环境哦)。

module.exports = {
  devServer: {
      compress: true,
  },
}

Proxy 代理

配置代理解决跨域问题,proxy 常用的配置属性如下:

  - target:代理的目标地址
  - pathRewrite:配置重写 URL
  - secure:默认不接受转发到 https 服务器,设置 secure 后开启支持。
  - changOrigin:默认情况下发送的 requestHeader 的 host 字段为本地服务地址,配置 changeOrigin 后 host 字段为正确的服务器地址。

简单配置:

// webpack.config.js
module.exports = {
    devServer: {
      proxy: {
      // 配置 /api 开启的请求代理到指定目标
        '/api': 'http://www.baidu.com',
    }
  },
}

// 发送请求处, 不要写域名
fetch('/api/list').then((res) => res.json()).then((res) => {
  console.log('fetch', res);
})

配置为对象:

module.exports = {
    devServer: {
      proxy: {
        '/api': {
          target: 'http://www.baidu.com',
        pathRewrite: {
          // 将 /api 开头的地址中的 api 替换为空
            '^/api': '',
        },
        changOrigin: true,
      },
    }
  },
}

changOrigin 图解:
假设 devServer 开启的本地服务地址 http://localhost:8000、服务器地址 http://localhost:3000, 为开启 changOrigin 时服务器收到的 requestHeaders 如下图:
image.png
这个 host 字段就是本地服务的地址,而开启了 changeOrigin 后 host 属性就变成正常的服务器地址了:
image.png

historyApiFallback

当我们使用 Vue、React 开发时,如果用到的路由模式时 H5 history 模式,当我们跳转到指定 path 后刷新页面,这时就会发现页面找不到了 404,这时因为浏览器会根据这个 url 去服务器寻找对应的文件,因为路由是在前端控制的,服务器并没有对应的文件所以就 404 了,这时我们就可以配置这个字段将其设置为 true,这样当本地服务器找不到对应得文件时,会将 index.html 返回,然后 Vue、React 会解析并跳转到指定路由。

module.exports = {
    devServer: {
      historyApiFallback: true,
  },
}

也可以对 historyApiFallback 进行更细致的配置:

module.exports = {
  //...
  devServer: {
    historyApiFallback: {
      rewrites: [
        // 当在 from 下刷新时, 返回 to 的内容
        { from: /^\/$/, to: '/views/landing.html' },
        { from: /^\/subpage/, to: '/views/subpage.html' },
        { from: /./, to: '/views/404.html' },
      ],
    },
  },
};

resolve 模块解析

当我们使用 require、import 方式导入模块时 webpack 会根据地址进行如下的解析:

  • 绝对路径:不需要进一步解析
  • 相对路径:import、require 的资源文件所处目录拼接上 import、require 给定的相对路径,生成决定路径。
  • 模块路径:也就是 require('vue') / import vue from 'vue' , 这时会在配置文件的 resolve.modules 指定的所有目录中检索,默认值为 ['node_modules']

配置:

module.exports = {
  resolve: {
      modules: ['node_modules'],
  }
}

文件和文件夹解析规则

我们导入一个文件可能会写出如下代码:

require('./js/index'); // 不带扩展名
require('./js'); // 只有文件夹名称

webpack 会判断引入的资源是一个文件还是一个文件夹,如果是一个文件则判断是否具有扩展名,有扩展名直接打包,没有则去 resolve.extensions 配置中查找文件扩展名解析。

module.exports = {
    resolve: {
      extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx'],
  }
}

如果是文件夹则会根据 resolve.mainFiles 配置中指定的文件顺序查找(默认为 index),然后再匹配 resolve.extensions 规则:

module.exports = {
    resolve: {
      mainFiles: ['index']
  }
}

alias 配置

alias 为一个目录地址配置一个别名,这样导入模块的时候可以直接使用这个别名,而不用书写大量的 ../。

const path = require('path');

module.exports = {
    resolve: {
      alias: {
        '@': path.resolve(__dirname, './src');
    }
  },
}

使用:

require('@/xxx.js');
// or
import xx from '@/xxx.js';