自我沉淀webpack5+react+eslint+tslint

2,963 阅读17分钟

自我沉淀webpack5+react+eslint+tslint

自己学了一段时间webpack准备将自己的知识沉淀一下,千里之行始于足下,如果不沉淀一下的话总是觉得知识点有些浮躁,无法彻底沉淀下来。

webpack 和webpack-cli 版本为

 "webpack": "^5.68.0",
 "webpack-cli": "^4.9.2",

一、 初始化webpack配置。

第一步 初始化webpack

先创建一个文件,名字自定义,例:webpackdemo
1、执行npm init 一路回车最后输入yes 就可以初始化package.json   如图
2、执行npm init -y 也可以初始化package.json。
12 二选其一即可。

image.png 第二步 创建文件

  在根目录创建文件夹src/index.js和index.html
  文件目录如下:

image.png

第三步创建我们自己的webpack配置文件

  在跟路径下面创建webpack.config.js 
  如图:

image.png

二、webpack自我总结大致分为几类配置

1、mode (环境)

mode分为两种模式

mode:"production",   生产环境

mode:"development",   开发环境

2、entry (入口)

entry:'./src/index.js',  // 入口根据自己的实际情况而定

多入口格式

特点:如果有一个入口最终就只有一个bundle文件。
     如果有两个入口就会产生两个bundle文件。
entry:{
    mian:'./src/main.js',
    test:'./src/test.js',
}
output(出口) 如果配置了多入口模式需要配置chunkFilename: '[id].js' 
来产生多个bundle(打包过后的)文件。

react入口文件内容

import React from 'react';
import { render } from 'react-dom';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/lib/locale/zh_CN';
import App from './containers/App';

render(
  <ConfigProvider locale={zhCN}><App /></ConfigProvider>,
  document.getElementById('app'),  // app为html文件中的id
);

if (module.hot) {
  module.hot.accept();
}

html文件内容 Hello World

// id="app" 对应index.js文件中的document.getElementById('app')

3、output(出口)

文件输出

webpack地址:webpack.docschina.org/configurati…

 const {resolve}=require('path');    // node环境的方法,可以获取到当前项目的路径
 ...
 ouput:{
     filename:'js/main.js',          // 最终会在打包的文件夹中产生js/main.js的文件
                                     // 如果怕产生冲突的话可以使用chunk文件格式 js/[hash:5].js。这样会产生hash前5位的文件名
     path:resolve(__dirname,'dist'), // 设置build之后产生的文件夹名称'dist',意为在项目的根路径创建dist文件。
     clean:true,                     // 每一次build之后将原文件删除再重新创建一个文件。
 }

4、module

配置

解析文件 比如将es5+转换为es5、less转换为css等等...

module:{
    rules:[
    
        // webpack地址:https://webpack.docschina.org/loaders/babel-loader/
        // 需要安装  npm install @babel/core @babel/preset-env @babel/preset-react -D  
        // 如果npm网速过慢的话可以使用cnpm(淘宝镜像)或者yarn。
        {
            test: /\.(js|jsx)$/,                // 匹配结尾是js/jsx的文件
            include: resolve(__dirname,'src'),  // 从src路径下面开始寻找,节省解析时间
            exclude: /node_modules/             // 不解析node_modules文件
            exclude: 'pre'                      // pre:优先执行 post:延后执行 不设置enforce则顺序执行
            use: [
                {
                  loader:'babel-loader',
                  options:{
                      presets:[
                          '@babel/preset-env',      // 将es5+转换成es5
                          '@babel/preset-react',    // 将react中的jsx语法转换成js语法
                      ],
                      cacheDirectory: true,  // 缓存:第二次编译时,会读取之前的缓存,节省编译时间
                  }
                }
            ]
        },
        // 使用ts-loader做转换
        // 需要安装  npm install ts-loader -D  
        { 
            test: /\.(tsx|ts)?$/, 
            loader: 'ts-loader',
            exclude: /node_modules/ 
        },
        // 如果匹配到css文件,则将css转换为style
        // 需要安装  npm install style-loader css-loader -D  
        {
            test: /\.css$/i,
            use:['style-loader','css-loader']
            options: {
              modules: true,  // 启动css模块功能
            },
        },
        
        
        // 如果匹配到以less文件,则使用less-loader转换成css,再通过css-loader转换成style,再通过style-loader转换成行内样式。
        // 因为上方已经安装过了style-loader和css-loader所以这里只需安装less-loader  cnpm install less-loader -D 
        {
            test: /\.less$/i,
            use:['style-loader','css-laoder','less-laoder'],
        },
        
        
        // 如果匹配到以sass或者scss结尾的文件则使用sass-loader和sass去做转译.
        // 同样的上方安装过style-loader和css-loader 则现在只需安装 cnpm install sass sass-loader -D
        {
            test: /\.s[ac]ss$/i,
            use:['style-loader','css-loader','sass-loader']
        },
        
        
        // 解析各种图片,需要一个file-laoder插件。
        // cnpm install file-loader -D
        {
            exclude: /.(html|less|css|sass|js|jsx|ts|tsx)$/,  // 排除哪些文件。
            test: /\.(jpg|jpe|png|gif)$/,
            loader: 'file-loader',
            opions: {
              name: 'imgs/[name].[ext]', 
              outputPath: 'other'        // 输出到dist中的文件名 
            }
        },
        
        
        {
            test: /\.(ect|ttf|svg|woff)$/,
            use: {
              loader: 'file-loader',
              options: {
                name: 'icon/[name].[ext]'
              }
            }
        }
    ]
}

5、resolve(解析模块规则别名)

webpack地址: webpack.docschina.org/configurati…

resolve:{
    alias:{
        "@":reslove(__dirname,'src/'), // 使用@代表从src/下方开始查找。  省略了../../ 繁琐方式
   },
   extensions:[".js",".jsx",".ts",".tsx"],  // 省略文件路径的后缀名。 index.jsx=index  index相当于index.jsx.
   mainFiles:["index"],  // 省略文件名。demo/index.js=demo
}

6、plugins

plugins主要是添加一些插件,比如压缩css文件、将css提取出来放在一个文件中。 webpack地址:webpack.docschina.org/plugins/

plugins:[
  // 使用HtmlWebpackPlugin去自动生成html文件。 需要安装
  new HtmlWebpackPlugin({
      // 指定html文件。如果没有指定则会自动生成一个html文件
      template: './index.html',  // 例:在第二步的时候创建的index.html文件。
      title: 'My App',
      filename:'index.html' // 指定输出文件名字
  }),
  

  
  // 显示百分比编译  样式如图1。
  // 需要引入const webpack = require('webpack')。
  // 不需要安装webpack。
  new webpack.ProgressPlugin({
      activeModules: false,
      entries: true,
      modules: true,
      modulesCount: 5000,
      profile: false,
      dependencies: true,
      dependenciesCount: 10000,
      percentBy: 'entries',
  }),
  
  
  // 将所有的css文件提取出来到一个css文件。
  // 需要下载  mini-css-extract-plugin插件  cnpm install --save-dev mini-css-extract-plugin
  // 引入方式 const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  // 引入了 mini-css-extract-plugin之后在生产环境中要将module中的style-loader更换成 MiniCssExtractPlugin.loader 才可以将css抽取出来并解析。
  // webpack 地址 https://webpack.docschina.org/plugins/mini-css-extract-plugin/
  new MiniCssExtractPlugin({
    filename: 'css/main.css'  // 设置dist文件中的css文件路径
  }),
  
  
  //  压缩css文件CssMinimizerPlugin
  //  需要下载 cnpm install css-minimizer-webpack-plugin -D
  //  引用方式 const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
  new CssMinimizerPlugin(),  // 启动css压缩
  
  
  // 将文件中的某个文件或者文件夹copy到build文件中
  // 需要下载 cnpm install copy-webpack-plugin -D
  // 引用方式 const CopyPlugin = require("copy-webpack-plugin");
  new CopyPlugin({
      patterns: [
        // { from: "source", to: "dest" },
        // 将publice文件夹下的manifest.json文件copy到build文件夹中
        { from: "public/manifest.json", to: "manifest.json" },
      ],
    }),
  //  添加eslint校验
  //  安装 npm install eslint-webpack-plugin -D
  //  使用 const ESLintPlugin = require('eslint-webpack-plugin');
  //  eslint 规则配置参考 下面的.eslintrc.js模块 添加了.eslintrc.js eslint插件会自动加载.eslintrc.js文件中的规则
  new ESLintPlugin({
      fix: true /* 自动帮助修复 */,
      extensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'react'],
      exclude: 'node_modules',
    }),
]


(图1) image.png

7、devtool 配置source-map

webpack地址 webpack.docschina.org/configurati…

一种提供源代码到构建后代码映射技术(如果构建后代码出错了,通过映射可以追踪源代码错误) source-map指的是使用可以在调试过程中向开发者展示源码,如果不开启source-map开发者查看时就会展示编译过的代码不利于开发者开发。

 /** 
   * 配置:[inline-|hidden-|eval-][nodources-][cheap-[module-]]source-map
   * source-map               外部    错误代码准确信息和源代码错误信息。
   * inline-source-map        内联    打包到文件中打包速度快。文件体积大。错误代码准确信息和源代码错误信息,只生成一个source-map
   * eval-source-map          内联    打包到文件中,每一个js文件都会生成source-map文件,错误代码准确信息和源代码错误信息会多一个hash值。
   * hidden-source-map        外部    打包到外部,但是打包速度慢,文件体积小。可以提示到错误代码的原因,但是没有错误位置,不能追踪到源代码的位置,只能提示到构建后代码的错误位置。
   * nocource-source-map      外部    错误代码的准确信息,但是没有任何源代码的信息。为了隐藏源代码
   * checp-module-source-map  外部    只精确到行
 */
  devtool:"inline-source-map",

8、devServer 制定启动服务的配置

weback 地址 webpack.docschina.org/configurati…

devServer:{
    hot:true,         // 是否开启HMR(热更新)模式,保存之后不需要重新打包就可以进行热更新。
    port:3000,        // 指定启动项目的端口号
    host:'127.0.0.1', // 指定启动项目的host地址
    compress:true,    // 是否开启gzip压缩
    open:true,        // 启动项目之后是否自动打开新的浏览器窗口
    proxy: {          // 代理 解决开大环境的跨域问题
                      // 一旦服务器接收到/api的请求就会把请求转发到另外一个服务器(http://localhost:8000)   
                      // 浏览器和服务器之间存在跨域,但是服务器和服务器之间不存在跨域
      '/api': {
        target: 'http://localhost:8000',
        pathRewrite: { '^/api': '' },   // 发送请求时,请求路径重写将 /api/xxxx 转换为 /xxx 去掉api
        secure: false      //  默认情况下,将不接受在 HTTPS 上运行且证书无效的后端服务器。 如果需要,可以这样修改配置
      }
    },
},

9、optimization 默认压缩工具

在生产环境不需要配置,会默认开启压缩 webpack地址 webpack.docschina.org/configurati…

   通过TerserPlugin覆盖默认压缩工具
   cnpm install  terser-webpack-plugin -D
   使用方式 const TerserPlugin = require('terser-webpack-plugin'); // 压缩js打包文件 优化build速度、优化start速度
   
    optimization: {
        minimizer: [new TerserPlugin({   // 通过配置TerserPlugin插件
          test: /\.(js|jsx|ts|tsx)$/,    // 针对js、jsx、ts、tsx文件去压缩
          exclude: /node_modules/,       // 排出掉node_modules文件,使star速度更快
          extractComments: true,
        })],
        usedExports: true,
        sideEffects: false,
        splitChunks: {
        chunks: 'async',
        minSize: 20000,
        minRemainingSize: 0,
        minChunks: 1,
        maxAsyncRequests: 30,
        maxInitialRequests: 30,
        enforceSizeThreshold: 50000,
        cacheGroups: {
          defaultVendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10,
            reuseExistingChunk: true,
          },
          default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true,
          },
        },
      },
    }

上面就是webpack的几项基本配置。有兴趣的同学可以看一下webpack官网,里面有中文版文档 webpack.docschina.org/

三、 根据环境区分不同的webpack配置

1、根据开发环境区分

上面讲到过环境分为。development(开发环境)和 production (生产环境)

    开发环境和生产环境的侧重点是不同的
  1、开发环境注重start的速度和方便调试。
  2、生产模式则主要侧重于build之后文件的大小,以及build的速度。
  
  因为在提取css文件会消耗大量的时间,所以在开发模式下直接使用style-loader

2、开发环境配置

下面我就不详细写注释了直接上配置

const webpack = require('webpack');
const {resolve}=require('path');
const HtmlWebpackPlugin=require('html-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');

module.exports={
  entry:'./src/index.js',
  output:{
    filename:'js/main.js',
    path:resolve(__dirname,'dist'),
    clean:true,
  },
  
  resolve:{
    alias: {
      "@": resolve(__dirname, 'src/')
    },
    extensions: [".js", ".jsx", ".json", '.ts', '.tsx'],
    mainFiles: ["index"]
  },
  
  mode:'development',
  
  devtool:'inline-source-map',
  
  devServer:{
    hot:true,
    port:3002,
    host:'127.0.0.1',
    compress:true,
    open:true,
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        pathRewrite: { '^/api': '' },
        secure: false
      }
    },
  },
  
  module:{
    rules:[
      {
        test:/\.(js|jsx)$/,
        include: resolve(__dirname,'src'),
        exclude: /node_modules/,
        enforce: 'pre',
        use:[{
          loader:'babel-loader',
          options:{
            presets: [
              '@babel/preset-env',
              '@babel/preset-react',!
            ],
            cacheDirectory: true,
          }
        }]
      },
      {
        test:/\.(ts|tsx)$/,
        loader:'ts-loader',
        exclude: /node_modules/
      },
      // 因为开发环境不需要压缩css文件,开启压缩css文件插件会导致start时间过长。
      {
        test:/\.css$/i,
        use:['style-loader','css-loader'],
      },
      {
        test:/\.less$/i,
        use:[
            'style-loader', 
            'css-loader'
            'less-loader'
           ],
      },
      {
        test:/\.s[ac]ss$/i,
        use:[
            'style-loader',
            'css-loader'
            'sass-loader'
            ]
      },
      {
        exclude: /.(html|less|css|sass|js|jsx|ts|tsx)$/,
        test: /\.(jpg|jpe|png|gif)$/,
        loader: 'file-loader',
        options: {
          name: 'imgs/[name].[ext]',
          outputPath: 'other'
        }
      },
      {
        test: /\.(ect|ttf|svg|woff)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: 'icon/[name].[ext]'
          }
        }
      }
    ]
  },
  
  plugins:[
    new HtmlWebpackPlugin({
      template: './index.html',
      title: 'My App',
      minify: {
        removeAttributeQuotes: true,
        removeComments: true,
      }
    }),
    // 显示百分比编译
    new webpack.ProgressPlugin({
      activeModules: false,
      entries: true,
      modules: true,
      modulesCount: 5000,
      profile: false,
      dependencies: true,
      dependenciesCount: 10000,
      percentBy: 'entries',
    }),
    new ESLintPlugin({
      fix: true /* 自动帮助修复 */,
      extensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'react'],
      exclude: 'node_modules',
    }),
  ],
}

3、生产环境配置

如果将mode设置为生产环境(production) webpack会自动开启tree shaking功能
并且我们要将css文件提取出来,
/**
 * tree shaking   去除无用代码
 * 前提:1、必须使用ES6代码
 *      2、开启production环境会自动开启tree shaking
 * 作用:减少代码体积
 * 
 * 在package.json中配置
 *  sideEffects:false  所有代码都没有副作用    (都可以进行tree  shaking)
 *   问题:可能会把css/@babel/polyfile (副作用)文件干掉
 *   解决:"sideEffects":["*.css"]
 * */
const webpack = require('webpack');
const {resolve}=require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const ESLintPlugin = require('eslint-webpack-plugin');

const cssloader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    loader: 'postcss-loader',
    options: {
      postcssOptions: {
        plugins: [
          ['autoprefixer'],
          require('postcss-preset-env')()
        ]
      }
    }
  }
];

// 开启css module   注意不能在css文件转换规则中使用,因为如果在css中使用则会影响到antd的样式。
const cssModuleloader = [
  // 将css提出的方法替换style-loader
  MiniCssExtractPlugin.loader,
  {
    loader: 'css-loader',
    options: {
      modules: {
        auto: (resourcePath) => resourcePath.endsWith('.less'),  // 只对.less结尾的文件进行模块化
        localIdentName: '[local]_[hash:base64:5]',
      },
    },
  },
  {
    loader: 'postcss-loader',
    options: {
      postcssOptions: {
        plugins: [['autoprefixer'], require('postcss-preset-env')()],
      },
    },
  },
];

module.exports ={
  mode: 'production',
  entry:'./src/index.js',
  output:{
    filename:'js/main.js',
    path:resolve(__dirname,'dist'),
    clean:true,
  },
  resolve:{
    alias: {
      "@": resolve(__dirname, 'src/')
    },
    extensions: [".js", ".jsx", ".json", '.ts', '.tsx'],
    mainFiles: ["index"]
  },
  
  module: {
    rules: [
      {
        test:/\.(js|jsx)$/,
        include: resolve(__dirname,'src'),
        exclude: /node_modules/,
        enforce: 'pre',
        use:[{
          loader:'babel-loader',
          options:{
            presets: [
              '@babel/preset-env',
              '@babel/preset-react',
            ],
            cacheDirectory: true,
          }
        }]
      },
      {
        test: /\.css$/i,
        use: [...cssloader]
      },
      {
        test: /\.less$/,
        use: [...cssModuleloader, 'less-loader'],
      },
      {
        test: /.s[ac]ss$/,
        use: [...cssModuleloader, 'sass-loader'],
      },
      {
        exclude: /.(html|less|css|sass|js|jsx|ts|tsx)$/,
        test: /\.(jpg|jpe|png|gif)$/,
        loader: 'file-loader',
        options: {
          name: 'imgs/[name].[ext]',
          outputPath: 'other'
        }
      },
      {
        test: /\.(ect|ttf|svg|woff)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: 'icon/[name].[ext]'
          }
        }
      }
    ]
  },
  
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/main.css'
    }),
    new CssMinimizerPlugin(),
    new webpack.ProgressPlugin({
      activeModules: false,
      entries: true,
      modules: true,
      modulesCount: 5000,
      profile: false,
      dependencies: true,
      dependenciesCount: 10000,
      percentBy: 'entries',
    }),
    new ESLintPlugin({
      fix: true /* 自动帮助修复 */,
      extensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'react'],
      exclude: 'node_modules',
    }),
  ],
  optimization: {
    usedExports: true,
    minimize: true,
  }
}

4、 开启css-module

开启css_modules

css-module 作用 大家都知道相同的类名是可以覆盖的,如果我们在不同的css文件中使用了相同的css类名 那么后者就会将前者覆盖掉,这样对开发者十分不友好(想类名也是很累的)。

于是css-module应运而生,他将css模块化,在后面自动添加一写hash值以保证不会重复。

下面就是css-loader开始module的配置 将下面的配置替换到css-loader上面

!注意:!注意: 这个配置不能用于匹配css文件中。
:原因::原因: 如果项目中使用的是antd组件库的话,因为antd组件库使用的是css文件,如果写在匹配css文件的配置中就会造成antd组件样式不生效。

官网例子:这种配置会生成一堆编码,开发者想要知道样式名字很费时间,所以我修改了一下

 {
    loader: 'css-loader',
    options: {
      modules: true,
      importLoaders: 1,
    },
  },

优化后的例子: local表示自己定义的类名,后面增加5位hash值 ,这样只需要看local值就可以了。

{
    loader: 'css-loader',
    options: {
      modules: {
        auto: (resourcePath) => resourcePath.endsWith('.less'), // 只对.less结尾的文件进行模块化。
        localIdentName: '[local]_[hash:base64:5]', 
      },
    },
  },

四、 优化

1、区分环境

我们不同的环境使用的mode也是不同的。可以在package.json中进行指定mode,在webpack中获取

package.json

"dev": "export NODE_ENV=development && npx webpack serve --config config/webpack.dev.js",
"build": "export NODE_ENV=production && npx webpack   --config config/webpack.prod.js"

webpack.common.js

process.env.NODE_ENV 则可以获取到环境变量

const devMode = process.env.NODE_ENV === 'production';

2、 加工重复的部分

仔细读文章的同学一定也会发现生产环境和开发环境中有些配置是冲突的 咱们可以提取一下,将公共的配置提取到一个common的文件中。

webpack.common.js

/**
 * HMR hot module replacement 热模块替换 / 模块热替换
 * 作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块)  可以极大的提升构建速度
 * 样式文件:可以使用HMR功能:因为style-loader内部实现了HMR功能   开发环境使用style-loader会使性能更好 打包速度更快
 * html文件:默认没有HMR功能,代表html不能热更新
 */
const webpack = require('webpack');
const {resolve}=require('path');
const HtmlWebpackPlugin=require('html-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
// 将css提取出来放到一个css文件中
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const devMode = process.env.NODE_ENV === 'production'; // 获取环境

const cssLoaders = [
  devMode ? MiniCssExtractPlugin.loader : 'style-loader',  // 通过环境变量判断生产环境使用MiniCssExtractPlugin.loader   开发环境使用 style-loader
  {
    loader: 'css-loader',
    options: {
      importLoaders: 1,
      modules: {
        auto: (resourcePath) => resourcePath.endsWith('.less'),
        localIdentName: '[local]_[hash:base64:10]',
      },
    },
  },
  {
    loader: 'postcss-loader',
    options: {
      postcssOptions: {
        plugins: [['autoprefixer'], require('postcss-preset-env')()],
      },
    },
  },
];


module.exports={
  entry:'./src/index.js',
  
  output:{
    filename:'js/main.js',
    path:resolve(__dirname,'dist'),
    clean:true,
  },
 
  resolve:{
    alias: {
      "@": resolve(__dirname, 'src/')
    },
    extensions: [".js", ".jsx", ".json", '.ts', '.tsx'],
    mainFiles: ["index"]
  },
  
  devtool: !devMode && 'inline-source-map',
  
  /**
   * devserver 配置参数
   * contentBase:'./dist'  运行代码的目录
   * compress:true  启动gzip压缩
   * prot:3000  指定端口号
   * host:'127.0.0.1'  指定域名
   * open:true  自动打开浏览器
   * hot:true   开启HMR功能(热更新)
   * whatchContentBase: true 监视contentBase目录下的所有文件,一旦文件有变化就会reload
   * whatchOptions:{   忽略监视的文件
   *                  ignored:/node_modules/
   *                }
   * overlay:false  如果出现错误不要全屏提示~
   * clientLogLevel:'none'  不要显示启动服务器的日志信息
   * quiet:true   除了一些基本的启动信息外不要打印其他内容
   * 服务器代理
   * proxy:{  解决开大环境的跨域问题
   *          一旦服务器接收到/api的请求就会把请求转发到另外一个服务器(3000)   浏览器和服务器之间存在跨域,但是服务器和服务器之间不存在跨域
   *         '/api': {
   *                target:'http://localhost:3000'
   *          },
   *          发送请求时,请求路径重写将 /api/xxxx 转换为 /xxx 去掉api
   *          pathRewite:{
   *               '^/api':''
   *          }
   *      }
   */
  devServer: {
    hot: true,
    port: 3002,
    host: '127.0.0.1',
    compress: true,
    open: true,
    // proxy: {
    //   '/api': {
    //     target: 'http://localhost:8000',
    //     pathRewrite: { '^/api': '' },
    //     secure: false
    //   }
    // },
  },
 
  
  module:{
    rules:[
      {
        /**
         * lazy loading懒加载
         */
        test: /\.(js|jsx)$/,
        include: resolve(resolve(__dirname, '..'), 'src'),
        exclude: /node_modules/,
        /**
         *  pre:优先执行
         *  post:延后执行
         *  不设置enforce则顺序执行
         */
        enforce: 'pre',
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env', '@babel/preset-react'],
              // 缓存:第二次构建时,会读取之前的缓存
              cacheDirectory: true,
            },
          },
        ],
      },
      {
        test:/\.(ts|tsx)$/i,
        loader:'ts-loader',
        exclude: /node_modules/
      },
      {
        test:/\.css$/i,
        use:[...cssLoaders],
      },
      {
        test:/\.less$/i,
        use:[...cssLoaders,'less-loader'],
      },
      {
        test:/\.s[ac]ss$/i,
        use:[...cssLoaders,'sass-loader']
      },
      {
        exclude: /.(html|less|css|sass|js|jsx|ts|tsx)$/,
        test: /\.(jpg|jpe|png|gif)$/,
        loader: 'file-loader',
        options: {
          name: 'imgs/[name].[ext]',
          outputPath: 'other'
        }
      },
      {
        test: /\.(ect|ttf|svg|woff)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: 'icon/[name].[ext]'
          }
        }
      }
    ]
  },
  
  plugins:[
    new HtmlWebpackPlugin({
      template: './index.html',
      title: 'My App',
      minify: {
        removeAttributeQuotes: true,
        removeComments: true,
      }
    }),
    // 显示百分比编译
    new webpack.ProgressPlugin({
      activeModules: false,
      entries: true,
      modules: true,
      modulesCount: 5000,
      profile: false,
      dependencies: true,
      dependenciesCount: 10000,
      percentBy: 'entries',
    }),
    new ESLintPlugin({
      fix: true /* 自动帮助修复 */,
      extensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'react'],
      exclude: 'node_modules',
    }),
  ],
}

3、 开发环境: 这时需要一个插件webpack-merge

使用方法:

const {merge}=require('webpack-merge');
const {merge}=require('webpack-merge');
const common=require("./webpack.common");

开发环境文件: webpack.dev.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common');
module.exports = merge(common, {
  mode: process.env.NODE_ENV,
});

4、生产环境

生产环境文件: webpack.prod.js

/**
 * tree shaking   去除无用代码
 * 前提:1、必须使用ES6代码
 *      2、开启production环境会自动开启tree shaking
 * 作用:减少代码体积
 *
 * 在package.json中配置
 *  sideEffects:false  所有代码都没有副作用    (都可以进行tree  shaking)
 *   问题:可能会把css/@babel/polyfile (副作用)文件干掉
 *   解决:"sideEffects":["*.css"]
 * */
 
//  将所有的css提取到同一个文件夹中
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 压缩css文件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const {merge}=require('webpack-merge');
const common=require("./webpack.common");

 module.exports = merge(common, {
  // 启动环境
  mode: process.env.NODE_ENV,

  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/main.css',
      chunkFilename: '[id].css',
    }),
    new CssMinimizerPlugin(),
  ],
});

5、文件格式

image.png

五、好啦。 配置已经完成,下面就可以运行项目啦:

我们在webpack.json文件中配置一下启动的命令 在webpack5中的启动命令和4中有些不一样 webpack.docschina.org/guides/gett…

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "export NODE_ENV=development && npx webpack server --config webpack.dev.js", // npx webpack server   执行start操作,开始运行项目    --config 指定使用哪一个文件启动
    "build": "export NODE_ENV=production && npx webpack --config webpack.prod.js"  // npx webpack 开始打包执行build操作
  },
  "browserslist": {
    // 配置兼容性 postcss会根据兼容性来在css文件中添加样式前缀  
    // 具体可以查阅browserslist文档https://github.com/browserslist/browserslist#queries
    "development": [
      "last 1 chrome version", // chrome最后一个大版本
      "last 1 firefox version",  // firefox 最后一个大版本
      "last 1 safari version"   // safari 最后一个大版本
    ],
    "production": [
      ">0.2%",  // 忽略市场上使用率小于0.2%的浏览器
      "not dead",  // 忽略已经死亡浏览器
    ]
  },

六、别忘了还有eslint和tslint校验以及css校验哦:本文使用airbnb规则

1、eslint

详情eslint+prettier 首先通过命令创建.eslintrc.js文件。 执行

   eslint --init

会自动安装下方依赖

eslint-plugin-import@2.25.4
eslint-plugin-jsx-a11y@6.5.1
eslint-config-airbnb@19.0.4
eslint-plugin-react@7.28.0
eslint@8.8.0
eslint-plugin-react-hooks@4.3.0
@typescript-eslint/parser@5.11.0
@typescript-eslint/eslint-plugin@5.11.0

   eslint --init
   1、选择第三项  检查语法、发现问题并强制执行代码风格 如【图一】
   2、我们使用JavaScript 模块(导入/导出)如【图二】
   3、选择自己的一个模版语言。这里用的是react 如【图三】
   4、集成ts。如【图四】
   5、选择在浏览器中运行。如【图五】
   6、是否使用流行风格 如【图六】
   7、使用哪一种风格?这里使用airbnb 如【图七】
   8、以何种格式输出配置文件? 这里是用js
   9、是否下载依赖?  选择是
   10、下载完成并生成.eslintrc.js
   11、生成之后我们可以自定义一下配置 如下
   
   module.exports = {
      root: true,
      env: {
        node: true,
        browser: true,
        es6: true,
      },
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
        ecmaVersion: 12,
        sourceType: 'module',
        project: './tsconfig.json',
      },
      plugins: ['react', 'react-hooks', '@typescript-eslint'],
      extends: ['plugin:react/recommended', 'airbnb'],
      parser: '@typescript-eslint/parser',
      rules: {
        // 禁止使用 var
        'no-var': 'error',
        // 优先使用 interface 而不是 type
        '@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
        '@typescript-eslint/no-explicit-any': 'off',
        '@typescript-eslint/explicit-module-boundary-types': 'off',
        'react-hooks/rules-of-hooks': 'error',
        'react-hooks/exhaustive-deps': 'warn',
        'react/prop-types': 'off',
      },
    };

[图一] image.png [图二]

image.png

[图三] image.png

[图四] image.png

[图五] image.png

[图六] image.png

[图7] image.png

[图8] image.png

[图9]

image.png

2、tslint

安装 tslint @typescript-eslint/eslint-plugin typescript-eslint/parser ts配置文件 tsconfig.json

文件路径如图:

image.png

配置如下:

{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6", // 指定编译到的模块系统 es6
    "target": "es5", // 指定编译到的 es 版本 es5
    "strict": true, // 使用严格类型检查
    "jsx": "react", // 指定 jsx 语法 react
    "allowJs": true, // 允许使用 js 语法
    "allowSyntheticDefaultImports": true, // 允许使用类似 import React from 'react' 的语法
    "sourceMap": true,
    "moduleResolution": "Node",
    "baseUrl": "./",
    "paths": {
      "@": ["src/"]
    }
  },
  "include": ["./src/**/*"],
  "exclude": ["node_modules", "dist"],
  "lib": ["ES2021", "dom"],
  "rules": {
    "indent": [true, "spaces", 2],
    "interface-name": false,
    "no-consecutive-blank-lines": false,
    "object-literal-sort-keys": false,
    "ordered-imports": false,
    "quotemark": [true, "single"],
    "semicolon": [true, "never"],
    "trailing-comma": [true, { "multiline": "never", "singleline": "never" }]
  }
}

增加 custom.d.ts 有时引用svg和png图片的时候ts会找不到声明文件则使用:

    declare module "*.svg" {
      const content: any;
      export default content;
    }
    declare module "*.png" {
      const content: any;
      export default content;
    }

3、styleLint

配置文件

需要安装 cnpm install stylelint-config-standard -D 文件路径如图

image.png 配置如下:

    {
      "extends": "stylelint-config-standard"
    }
    
    

好了下面就要配置 .eslintignore eslint有时会校验一些我们不想让它校验的文件,我们可以加一下这个配置哦。

文件路径如图 image.png 配置如下:

    config/
    scripts/
    node_modules/
    \.eslintrc.js
    webpack.common.js
    webpack.dev.js
    webpack.prod.js
    
    

大功告成!!!!!!!!

下面附上完整的配置哦

文件目录

image.png

1、 .eslintignore

config/
scripts/
node_modules/
\.eslintrc.js
webpack.common.js
webpack.dev.js
webpack.prod.js

2、 .eslintrc.js

module.exports = {
  root: true,
  env: {
    node: true,
    browser: true,
    es6: true,
  },
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 11,
    sourceType: "module",
    project: "./tsconfig.json",
  },
  plugins: ["react", "react-hooks", "@typescript-eslint"],
  extends: [
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
  ],
  rules: {
    // 禁止使用 var
    "no-var": "error",
    // 优先使用 interface 而不是 type
    "@typescript-eslint/consistent-type-definitions": ["error", "interface"],
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn",
    "react/prop-types": "off",
  },
};

3、 .stylelintrc.json

{
  "extends": "stylelint-config-standard"
}

4、 less.d.ts

declare module '*.scss' {
  const classes: { [key: string]: string };
  export default classes;
}
declare module '*.less' {
  const classes: { [key: string]: string };
  export default classes;
}

5、 custom.d.ts

declare module "*.svg" {
  const content: any;
  export default content;
}
declare module "*.png" {
  const content: any;
  export default content;
}

6、 package.json

{
  "name": "demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
     "dev": "export NODE_ENV=development && npx webpack serve --config config/webpack.dev.js",
     "build": "export NODE_ENV=production && npx webpack   --config config/webpack.prod.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.15.0",
    "@babel/preset-env": "^7.15.0",
    "@babel/preset-react": "^7.14.5",
    "@types/react": "^17.0.19",
    "@typescript-eslint/eslint-plugin": "^5.11.0",
    "@typescript-eslint/parser": "^5.11.0",
    "antd": "^4.16.13",
    "autoprefixer": "^10.3.2",
    "babel-loader": "^8.2.2",
    "css-loader": "^6.2.0",
    "css-minimizer-webpack-plugin": "^3.0.2",
    "eslint": "^7.32.0",
    "eslint-plugin-react": "^7.28.0",
    "eslint-plugin-react-hooks": "^4.3.0",
    "eslint-webpack-plugin": "^3.1.1",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^5.3.2",
    "less": "^4.1.1",
    "less-loader": "^10.0.1",
    "mini-css-extract-plugin": "^2.2.0",
    "postcss-loader": "^6.1.1",
    "postcss-preset-env": "^6.7.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "^5.2.0",
    "sass": "^1.38.0",
    "sass-loader": "^12.1.0",
    "style-loader": "^3.2.1",
    "stylelint": "^13.13.1",
    "stylelint-config-standard": "^22.0.0",
    "terser-webpack-plugin": "^5.1.4",
    "thread-loader": "^3.0.4",
    "ts-loader": "^9.2.5",
    "typescript": "^4.3.5",
    "webpack": "^5.68.1",
    "webpack-cli": "^4.8.0",
    "webpack-dev-server": "^4.0.0",
    "webpack-merge": "^5.8.0",
    "tslint": "^6.1.3"
  },
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "5.2.0"
  },
  "browserslist": {
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ],
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ]
  },
  "eslintConfig": {
    "extends": "airbnb-base"
  }
}

7、 tsconfig.json

{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6", // 指定编译到的模块系统 es6
    "target": "es5", // 指定编译到的 es 版本 es5
    "strict": false, // 使用严格类型检查
    "jsx": "react",  // 指定 jsx 语法 react
    "lib": ["ES2021", "dom"], // 指定ts检测语法支持到哪一个EMAC版本
    "allowJs": true,  // 允许使用 js 语法
    "allowSyntheticDefaultImports": true, // 允许使用类似 import React from 'react' 的语法
    "sourceMap": true,
    "moduleResolution": "Node",
    "baseUrl": "./",
    "paths": {
      "@": ["src/"]
    }
  },
  "include": ["./src/**/*"],
  "exclude": ["node_modules", "dist"],
  "rules": {
    "indent": [true, "spaces", 2],
    "interface-name": false,
    "no-consecutive-blank-lines": false,
    "object-literal-sort-keys": false,
    "ordered-imports": false,
    "quotemark": [true, "single"],
    "semicolon": [true, "never"],
    "trailing-comma": [true, { "multiline": "never", "singleline": "never" }]
  }
}

8、 webpack.common.js

/**
 * HMR hot module replacement 热模块替换 / 模块热替换
 * 作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块)  可以极大的提升构建速度
 * 样式文件:可以使用HMR功能:因为style-loader内部实现了HMR功能   开发环境使用style-loader会使性能更好 打包速度更快
 * html文件:默认没有HMR功能,代表html不能热更新
 */
const webpack = require('webpack');
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');

const devMode = process.env.NODE_ENV === 'production';
const cssLoaders = [
  devMode ? MiniCssExtractPlugin.loader : 'style-loader',
  {
    loader: 'css-loader',
    options: {
      importLoaders: 1,
      modules: {
        auto: (resourcePath) => resourcePath.endsWith('.less'),
        localIdentName: '[local]_[hash:base64:10]',
      },
    },
  },
  {
    loader: 'postcss-loader',
    options: {
      postcssOptions: {
        plugins: [['autoprefixer'], require('postcss-preset-env')()],
      },
    },
  },
];

module.exports = {
  entry: './src/index.jsx',
  /** 多入口:   特点:如果有一个入口最终只有一个bundle
   *                如果有两个入口就有两个bundle
   * entry: {
   *   main: './src/index.js',
   *   test: './src/index.js',
   *  },
   */
  output: {
    filename: 'js/main.js',
    path: resolve(resolve(__dirname, '..'), 'dist'),
    clean: true,
  },
  /**
   * 解析模块规则别名
   */
  resolve: {
    /**
     * 使用 @ 代替路径 @ 代表从src/ 下面找路径
     */
    alias: {
      '@': resolve(resolve(__dirname, '..'), 'src/'),
    },
    /**配置省略文件路径的后缀名
     * index.jsx ===  index    index相当于index.jsx
     */
    extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
    /**配置省略文件路径的后缀名
     * demo/index.jsx ===  demo
     */
    mainFiles: ['index'],
  },

  devtool: !devMode && 'inline-source-map',

  devServer: {
    hot: true,
    port: 3002,
    host: '127.0.0.1',
    compress: true,
    open: true,
    // proxy: {
    //   '/api': {
    //     target: 'http://localhost:8000',
    //     pathRewrite: { '^/api': '' },
    //     secure: false
    //   }
    // },
  },
  module: {
    rules: [
      {
        /**
         * lazy loading懒加载
         */
        test: /\.(js|jsx)$/,
        include: resolve(resolve(__dirname, '..'), 'src'),
        exclude: /node_modules/,
        /**
         *  pre:优先执行
         *  post:延后执行
         *  不设置enforce则顺序执行
         */
        enforce: 'pre',
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env', '@babel/preset-react'],
              // 缓存:第二次构建时,会读取之前的缓存
              cacheDirectory: true,
            },
          },
        ],
      },
      {
        test: /\.tsx$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        use: [...cssLoaders],
      },
      {
        test: /\.less$/,
        use: [...cssLoaders, 'less-loader'],
      },
      {
        test: /\.s[ac]ss$/,
        use: [...cssLoaders, 'sass-loader'],
      },
      {
        exclude: /.(html|less|css|sass|js|jsx|ts|tsx)$/,
        test: /\.(jpg|jpe|png|gif)$/,
        loader: 'file-loader',
        options: {
          name: 'imgs/[name].[ext]',
          outputPath: 'other',
        },
      },
      {
        test: /\.(ect|ttf|svg|woff)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: 'icon/[name].[ext]',
          },
        },
      },
    ],
  },

  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      title: 'My App',
      minify: {
        removeAttributeQuotes: true,
        removeComments: true,
      },
    }),
    // 显示百分比编译
    new webpack.ProgressPlugin({
      activeModules: false,
      entries: true,
      modules: true,
      modulesCount: 5000,
      profile: false,
      dependencies: true,
      dependenciesCount: 10000,
      percentBy: 'entries',
    }),
    new ESLintPlugin({
      fix: true /* 自动帮助修复 */,
      extensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'react'],
      exclude: 'node_modules',
    }),
  ],
};

9、 webpack.dev.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common');
module.exports = merge(common, {
  mode: process.env.NODE_ENV,
});

10、 webpack.prod.js

/**
 * tree shaking   去除无用代码
 * 前提:1、必须使用ES6代码
 *      2、开启production环境会自动开启tree shaking
 * 作用:减少代码体积
 *
 * 在package.json中配置
 *  sideEffects:false  所有代码都没有副作用    (都可以进行tree  shaking)
 *   问题:可能会把css/@babel/polyfile (副作用)文件干掉
 *   解决:"sideEffects":["*.css"]
 * */
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// 压缩css文件
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { merge } = require('webpack-merge');
const common = require('./webpack.common');

module.exports = merge(common, {
  // 启动环境
  mode: process.env.NODE_ENV,

  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/main.css',
      chunkFilename: '[id].css',
    }),
    new CssMinimizerPlugin(),
  ],
});

11、 .gitignore

node_modules
dist

gihub项目地址

好累呀!!!! 终于完成。如有不对请指出。谢谢~~~~

后面会有vscode+eslint+prettier自动格式化的说明 欢迎大家来看