从0到1使用webpack搭建react脚手架

news2024/11/15 15:28:59

背景

好多前端童鞋工作多年依然不会使用webpack搭建react脚手架,本文就介绍下如何从零开始搭建一个属于你自己的前端脚手架,提高自己的工程化实力,同时也提高团队的开发效率。

一、基础配置

目标:可以启动最简单的react项目

初始化项目、安装依赖

  1. 初始化项目 npm init -y
  2. 然后按下方的目录结构创建文件
├── config
|   ├── webapack.base.js # 公共配置
|   ├── webpack.dev.js  # 开发环境配置
|   └── webpack.prod.js # 打包环境配置
├── public
│   └── index.html # html模板
├── src
|   ├── App.tsx 
│   └── index.tsx # react应用入口页面
├── tsconfig.json  # ts配置
└── package.json

安装依赖

// 1.安装webpack
pnpm i webpack webpack-cli -D

// 2.安装react和类型依赖
pnpm i react react-dom 
pnpm i @types/react @types/react-dom -D

添加html, App, index, tsconfig

  1. 添加public/index.html内容
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="icon" href="./favicon.ico ">
  <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
  1. 添加tsconfig.json内容
{
  "compilerOptions": {
    "target": "es5",
    // 指定要包含在编译中的 library  
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    // 允许 ts 编译器编译 js 文件  
    "allowJs": true,
    // 跳过类型声明文件的类型检查  
    "skipLibCheck": true,
    // es 模块 互操作,屏蔽 ESModule 和 CommonJS 之间的差异  
    "esModuleInterop": true,
    // 允许通过 import x from 'y' 即使模块没有显式指定 default 导出  
    "allowSyntheticDefaultImports": true,
    // 开启严格模式  
    "strict": true,
    // 对文件名称强制区分大小写  
    "forceConsistentCasingInFileNames": true,
    // 为 switch 语句启用错误报告  
    "noFallthroughCasesInSwitch": true,
    // 生成代码的模块化标准  
    "module": "esnext",
    // 模块解析(查找)策略  
    "moduleResolution": "node",
    // 允许导入扩展名为.json的模块  
    "resolveJsonModule": true,
    // 是否将没有 import/export 的文件视为旧(全局而非模块化)脚本文件  
    "isolatedModules": true,
    // 编译时不生成任何JS文件(只进行类型检查)  
    "noEmit": true,
    // 指定将 JSX 编译成什么形式  
    "jsx": "react-jsx"
  },
  // 指定允许ts处理的文件目录  
  "include": [
    "src"
  ]
}
  1. 添加src/App.tsx
import React from 'react';

export default function App() {
  return (
    <div>App</div>
  )
}
  1. 添加index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(<App />)

配置webpack

webpack公共配置

  1. 配置入口出口
// config/webpack.base.js
const path = require('path');

module.exports = {
  entry: path.resolve(__dirname, '../src/index.tsx'),
  output: {
    path: path.resolve(__dirname, '../dist'), // 打包后的文件存放的位置, 必须是绝对路径
    filename: 'js/[name].[chunkhash:8].js', // [name]格式化字符串,之前是啥名,现在还是啥名  2.[chunkhash:8]指定hash值,解决缓存问题
    clean: true, // 每次打包前清空dist目录
    publicPath: '/',  // 资源引用路径,若不配置,刷新页面,页面空白 如:/goods/detail ->/goods/js/main.js,找不到资源
  },
}
  1. 配置loader解析ts, jsx
pnpm i babel-loader @babel/core @babel/preset-env core-js @babel/preset-react @babel/preset-typescript -D
  1. babel-loader: 使用 babel 加载最新js代码并将其转换为 ES5
  2. @babel/corer: babel 编译的核心包
  3. @babel/preset-env: babel 编译的预设,可以转换目前最新的js标准语法
  4. core-js: 使用低版本js语法模拟高版本的库,也就是垫片

webpack.base添加module.rules

module.exports = {
 module: {
    rules: [
      {
        test: /\.(ts|tsx|js|jsx)$/, use: ['babel-loader'],
      },
    ]
  },
};

为了避免webpack配置文件过大,将babel-loader配置抽离到babel.config.js ,,使用js作为配置文件,可用process.env.NODE_ENV来区分是开发、打包模式。

在/babel.config.js中添加

module.exports = function (api) {
  api.cache(true);
  return {
    presets: [
      '@babel/preset-env',  // 兼容高版本js
      '@babel/preset-react',
      '@babel/preset-typescript'
    ],
  }
};

  1. 配置路径别名, 省略文件后缀名

**extensions **作用:在引入模块时不带文件后缀时

module.exports = {
  // ...
  resolve: {
    extensions: ['.js', '.tsx', '.ts', '.json'],  // 解析模块时,可以省略的扩展名
  },
}
  1. 添加html-webpack-plugin

作用:处理html的生成和管理,自动引入js,css。

  1. webpack-dev-server开发服务器,默认会找 /public 文件夹中的index.html,

先装包pnpm i html-webpack-plugin -D

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../', 'public/index.html'), // 指定template后div里的东西不会被删除
      inject: true, // 自动注入静态资源, 2可指定js插入位置,如body前
      title: 'react-webpack', // 设置页面title
      // favicon: path.resolve(__dirname, '../', 'public/favicon.ico'), // 设置页面图标
      // filename: 'aaa.html', // 打包后的文件名, 默认index.html
    }),
  ],
}

为项目配置标题和网站图标
在HtmlWebpackPlugin配置title和favicon后,index.html也需要配置

// 网站图标
<link rel="icon" href="./favicon.ico ">
// 页面标题
<title><%= htmlWebpackPlugin.options.title %></title>

基础的公共配置完毕,接着配置开发、打包环境

webpack开发环境配置

开发是需要在内存中打包,速度快。

  1. 配置webpack.dev.js

先装包pnpm i webpack-dev-server webpack-merge -D
配置webpack.dev.js

const path = require('path');
const { merge } = require('webpack-merge');
const base = require('./webpack.base.js');

module.exports = merge(base, {
  mode: 'development', // development:开发环境,内存打包  production:生产环境,硬盘打包
  devtool: 'eval-cheap-module-source-map', // 生成map文件,方便调试
  devServer: {
    open: false, // 自动打开浏览器
    port: 3344,
    hot: true, // 热更新
    historyApiFallback: true, // 解决history路由404问题
    compress: false, // gzip压缩,开发环境不开启,提升热更新速度
    static: {
      directory: path.join(__dirname, "../public"), //托管静态资源public文件夹
    },
  },
});
  1. package.json添加启动脚本
"scripts": {
  "start": "webpack serve -c ./config/webpack.dev.js",
}

至此,可看到项目启动起来了

webpack打包环境配置

  1. 处理webpack.prod.js
const path = require('path');
const { merge } = require('webpack-merge')
const base = require('./webpack.base.js')

module.exports = merge(base, {
  mode: 'production', // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
})
  1. package.jsonscripts中添加build打包命令
"scripts": {
  "build": "webpack -c ./config/webpack.prod.js",
},
  1. 如何使用浏览器查看打包后的项目

打包后的dist文件可以在本地借助node服务器serve打开,全局安装serve

npm i serve -g

然后在项目根目录命令行执行serve -s dist,就可以启动打包后的项目了

至此,简单版的react-cli 配置完毕。

二、进阶配置

展示打包进度-ProgressPlugin

ProgressPlugin是自带的,如果要美颜版,先装包pnpm i webpackbar -D

// webpack.base.js
const WebpackBar = require('webpackbar'); 

module.exports = {
  plugins: [
    // 2.开美颜
    new WebpackBar({
      color: "#85d", // 默认green,进度条颜色支持HEX
      basic: false, // 默认true,启用一个简单的日志报告器
      profile: false, // 默认false,启用探查器。
    })
  ]
}

自带的方式-一般不用

const { ProgressPlugin } = require('webpack');

module.exports = {
  plugins: [
    // 1.基础版
    new ProgressPlugin({})
  ]
}

将某插件配置到全局-不用再引React

  1. 将某个插件全局化,比如react,这样组件中就不用导入了。
  2. webpack自带插件ProvidePlugin来处理

背景
image.pngimage.png

解决方案:配置webpack.base

const { ProvidePlugin } = require('webpack');

module.exports = {
  plugins: [
    new ProvidePlugin({
      React: path.resolve(__dirname, '../', 'node_modules/react/index.js'),
    })
  ]
}

配置路径别名

  1. 处理webpack.base.js
module.exports = {
  resolve: {
    alias: {
      '@': path.resolve(__dirname, '../', 'src'),
    },
  }
}
  1. 处理tsconfig.json, 添加baseUrl,paths
{
  "compilerOptions": {
    // ...
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
  }
}

复制public文件夹资源

背景:组件中用public的图片,打包后不显示
原因:打包时不会将public的资源放入dist
解决方案:用插件将public资源复制到dist

  1. 先装包 pnpm i copy-webpack-plugin -D
  2. 配置webpack.prod.js
const CopyPlugin = require('copy-webpack-plugin');
module.exports = merge(base, {
  plugins: [
    // 复制public文件夹
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, '../public'), // 复制public下文件
          to: path.resolve(__dirname, '../dist'), // 复制到dist目录中
          filter: source => {
            return !source.includes('index.html') // 忽略index.html
          }
        },
      ],
    }),
  ],
});

配置sourcemap

作用:控制台报错的位置和源码中的位置一致(打包后行数映射到打包前的行数)。打包后会多个map文件
在开发、生产环境中配置sourcemap

  1. 开发环境
// webpack.dev.js
module.exports = {
  mode: 'development', // development:开发环境,内存打包  production:生产环境,硬盘打包
  devtool: 'eval-cheap-module-source-map', // 生成map文件,方便调试
}
  1. 生产环境
// webpack.prod.js
module.exports = {
  mode: 'development', // development:开发环境,内存打包  production:生产环境,硬盘打包
  devtool: 'source-map', // 生成map文件,方便调试
}

处理样式

  1. 从后往前处理,当webpck遇到css时,先用css-loader加载解析返回内容给style-loader
  2. style-loader: 将css作为内部样式插head标签中
  3. 用mini-css-extract-plugin抽离css,一般只在生产环境配置
  4. 总结:开发时用内部样式,上线时用外部样式。
  1. 先装包
pnpm i css-loader style-loader mini-css-extract-plugin -D  
  1. 配置webpack.base.js
module.exports = {
  module: {
    rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
    ]
  }
}
  1. 上线时抽离css,配置webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const prodConfig = {
  mode: 'production',
  module: {
    rules: [
      // 1. 使用MiniCssExtractPlugin.loader代替style-loader
      { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
    ]
  },
  plugins: [
    // 2. 提取css
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css',
    }),
  ],
};

module.exports = merge(base, prodConfig);

❌处理less

prod里需要重复配,待解决

pnpm i less less-loader -D 
// webpack.base.js
{ test: /\.(css|less)$/, use: ['style-loader', 'css-loader', 'less-loader'] },

// webpack.prod.js
{ test: /\.(css|less)$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] },

压缩css

  1. 抽离css后,不会自动压缩,需要处理
  1. 装包pnpm i css-minimizer-webpack-plugin -D
  2. 配置webpack.prod.js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = merge(base, {
  // ...
  optimization: {
    minimizer: [ 
      // 压缩css
      new CssMinimizerPlugin(),
    ],
  },
});

压缩js

  1. webpack在mode=prod时默认会压缩js, 但是手动压缩css后,默认压缩js会失效

解决方案

  1. 装包 pnpm i terser-webpack-plugin -D
  2. 配置webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin');

module.exports = merge(base, {
  // ...
  optimization: {
    minimizer: [ 
      // 压缩css
      new CssMinimizerPlugin(),
      // 压缩js
      new TerserPlugin({
        parallel: true, // 开启多线程压缩
        terserOptions: {
          compress: {
            // pure_funcs: ["console.log"] // 删除console.log
          }
        }
      }),
    ],
  },
});

处理css3前缀兼容

  1. 先装包
pnpm i postcss-loader autoprefixer -D
  1. 修改webpack.base.js
module.exports = {
  // ...
  module: { 
    rules: [
      // ...
      {
        test: /.(css|less)$/, //匹配 css和less 文件
        use: [
          // ...
          'postcss-loader',
          'less-loader',
        ]
      },
    ]
  },
  // ...
}
  1. 配置postcss

根目录新建postcss.config.js,postcss会自动读取配置

module.exports = {
  plugins: ['autoprefixer']
}
  1. 配置要兼容的浏览器和版本
// package.json
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }

🚩处理css module

注意:将css-loader,从v7 => “^6.0.0”,否则报错

方案1 - react-css-modules

提取lessModule还是有问题,临时解决方案:不提取cssModule

  1. 先装包 pnpm i babel-plugin-react-css-modules postcss-less -D
  2. 配置webpack.base.js
module.exports = {
  rules: [
    {
      test: /\.(ts|tsx|js|jsx)$/,
      use: [
        'thread-loader',
        {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
            plugins: [
              [
                'react-css-modules',
                {
                  exclude: 'node_modules',
                  filetypes: { '.less': { syntax: 'postcss-less' } },
                  generateScopedName: '[local]-[hash:base64:5]',
                },
              ].filter(Boolean),
            ],
          },
        }
      ],
      include: [path.resolve(__dirname, '../src')]
    },
    {
      test: /\.less$/,
      use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader']
    },
  ]
}
  1. 配置webpack.prod.js
module.exports = merge(base, {
  plugins: [
    new MiniCssExtractPlugin({
      ignoreOrder: true,
      filename: 'css/[name].[contenthash:8].css',
    })
  ],
  module: {
    rules: [
      {
        test: /\.less$/,
        // 注意:提取less时排除 module.less
        exclude: /\.module\.less$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'],
      },
    ]
  },
}

成功。

❌方案2 - css-loader

  1. 普通的css又不生效, 普通less和module.less分开处理 - 参考cra

  2. prod不做处理,即

  3. 配置webpack.base.js

module.exports = {
  module: 
    rules: [
      {
        test: /\.(css|less)$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: {
                auto: true,
                localIdentName: '[local]-[hash:base64:5]'
              }
            },
          },
          'postcss-loader', 'less-loader',
        ],
        // sideEffects: true,
      },
    ]
}
  1. 处理webpack.prod.js,注意:处理普通less文件时要排除less.module文件。
module.exports = merge(base, {
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css',
    })
  ],
  module: {
    rules: [
      {
        test: /\.less$/,
        exclude: /\.module\.less$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader'],
      }
    ]
  },
})

保留旧的方案:排除module.less

module.exports = {
  module: { reules: [
    // 处理普通less文件, 排除less.module文件
    { test: /\.less$/, exclude: /\.module\.less$/, use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader',] },
    // 处理普通less.module文件
    {
      test: /\.module\.less$/,
      use: [
        'style-loader',
        {
          loader: 'css-loader',
          options: {
            // 为样式指定名称, local原样式名,还可加[path][name]
            modules: { localIdentName: '[local]-[hash:base64:5]' }
          },
        },
        'postcss-loader',
        'less-loader',
      ],
    },
  ]}
}

处理react热更新

  1. form修改文案,会热更新-浏览器自动刷新,input中的状态丢失
  2. 期望:浏览器不刷新的热更新,保留form状态。
  1. 先装包pnpm i @pmmmwh/react-refresh-webpack-plugin react-refresh -D
  2. 配置webpack.dev.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = merge(base, {
  plugins: [
    new ReactRefreshWebpackPlugin(), // react热更新
  ]
});

处理图片

一、webpack5已内置图片处理

  1. 添加静态文件声明来处理找不到模块或其类型声明的报错。

二、webpack4的方案:已淘汰(需装包)

  1. file-loader,将图片整到内存中,build时可看到图片,但是名字变了。
  2. url-loader,将图片转化为base64,build时看不到图片,打到了js中
  1. 配置webpack.base.js
  module: {
    rules: [
      // 处理图片-webpack5,类似file-loader, 可通过generator配置图片的位置和名字
      { test: /\.(png|jpg|jpeg|gif|svg|webp)$/, type: 'asset/resource', generator: { filename: 'img/[name].[contenthash:8][ext]' } },
      // 处理图片-webpack4-已淘汰
      // { test: /\.(png|jpg|jpeg|gif|svg|webp)$/, use: 'file-loader' }
      // { test: /\.(png|jpg|jpeg|gif|svg|webp)$/, use: 'url-loader' }
    ]
  }
  1. 添加类型声明文件 src/index.d.ts
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
declare module '*.less'
declare module '*.css'

  1. 配置tsconfig.json
// 不用ts处理的文件目录 
{
  compilerOptions: ...,
  "exclude": [
    "node_modules",
    "dist",
    "static"
  ],
}

处理字体和媒体文件

处理webpack.base.js

// webpack.base.js
module.exports = {
  module: {
    rules: [
      // ...
      {
        test:/.(woff2?|eot|ttf|otf)$/, // 匹配字体图标文件
        type: "asset", // type选择asset
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 小于10kb转base64位
          }
        },
        generator:{ 
          filename:'static/fonts/[name][ext]', // 文件输出目录和命名
        },
      },
      {
        test:/.(mp4|webm|ogg|mp3|wav|flac|aac)$/, // 匹配媒体文件
        type: "asset", // type选择asset
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 小于10kb转base64位
          }
        },
        generator:{ 
          filename:'static/media/[name][ext]', // 文件输出目录和命名
        },
      },
    ]
  }
}

三、优化构建速度

❌分析打包速度

使用less后,分析打包速度报错。

  1. 装包pnpm i speed-measure-webpack-plugin -D
  2. 新建config/webpack.analy.js, 这样不会影响正常的开发、线上
const prodConfig = require('./webpack.prod.js') // 引入打包配置
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); // 引入webpack打包速度分析插件
const smp = new SpeedMeasurePlugin(); // 实例化分析插件
const { merge } = require('webpack-merge') // 引入合并webpack配置方法

// 使用smp.wrap方法,把生产环境配置传进去,由于后面可能会加分析配置,所以先留出合并空位
module.exports = smp.wrap(merge(prodConfig, {

}))

  1. 配置打包命令
  "scripts": {
    "start": "webpack serve  -c ./config/webpack.dev.js",
    "analy": "webpack -c ./config/webpack.analy.js"
  },
  1. 结果 - 使用less报错

image.png
image.png

缩小loader作用范围

  1. 一般第三库都是已经处理好的, 不需要再用loader去解析,可节省时间。

配置webpack.base.js

const path = require('path')
module.exports = {
  module: {
    rules: [
      {
        test: /.(ts|tsx)$/,
        // 缩小范围
        include: [path.resolve(__dirname, '../src')], 只对项目src文件的ts,tsx进行loader解析
      }
    ]
  }
}

🚩开启缓存

  1. webpack5内置缓存插件,可缓存生成的webpack模块和chunk,提速90%
  2. 缓存位置:node_modules/.cache/webpack,区分development和production缓存
// webpack.base.js
// ...
module.exports = {
  // ...
  cache: {
    type: 'filesystem', // 使用文件缓存
  },
}

开启多线程loader

  1. 由于thread-loader不支持抽离css插件MiniCssExtractPlugin.loader(下面会讲),所以这里只配置了多进程解析js
  2. loader 放其他 loader 之前。放在此后的 loader 会在一个独立的 worker 池中运行
  3. 注意:开启多线程需要启动时间, 约600ms左右,所以适合规模大的项目。
  1. 先装包 pnpm i thread-loader -D
  2. 配置webpack.base.js
module.exports = {
  module: {
    rules: [
      {
        test: /.(ts|tsx)$/,
        use: ['thread-loader', 'babel-loader']
      }
    ]
  }
}

其他优化配置

除了上面的配置外,webpack还提供了其他的一些优化方式,本次搭建没有使用到,所以只简单罗列下:

  • externals: 外包拓展,打包时会忽略配置的依赖,会从上下文中寻找对应变量;
  • module.noParse: 匹配到设置的模块,将不进行依赖解析,适合jquery,boostrap这类不依赖外部模块的包;
  • ignorePlugin: 可以使用正则忽略一部分文件,常在使用多语言的包时可以把非中文语言包过滤掉;

四、优化构建结果

分析打包体积

  1. pnpm start和build时都会自动打开包体积分析页面 http://127.0.0.1:8888/
  2. 不用时注释掉 **new BundleAnalyzerPlugin() **即可
  1. 装包pnpm install webpack-bundle-analyzer -D
  2. 配置webpack.base.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

🚩提取第三方包和公共模块

  1. cacheGroups可继承外边的属性
  2. 注意优先级

配置webpack.prod.js

module.exports = {
  // ...
  optimization: {
    // ...
    // 提取第三方包和公共模块
    splitChunks: { 
      chunks: 'all',
      minSize: 10, // 提取代码体积大于10就提取出来
      cacheGroups: {
        vendors: { // 提取node_modules代码
          name: 'vendors', // 名称.hash.js
          test: /[\\/]node_modules[\\/]/,  // 只匹配node_modules里面的模块
          minChunks: 1, // 只要使用一次就提取出来
          priority: -10, // 注意优先级,如果过高下边不生效
          reuseExistingChunk: true,
        },
        commons: { // 提取页面公共代码
          name: 'commons', // 提取文件命名为commons
          minChunks: 2, // 只要使用两次就提取出来
          priority: -20, // 注意优先级,如果过高下边不生效
          reuseExistingChunk: true,
        },
      }
    }, 
  }
}

如何抽离react、antd, 接上方

      cacheGroups: {
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)/,
          name: 'react',
          reuseExistingChunk: true,
        },
        antd: {
          name: 'antd',
          test: /[\\/]node_modules[\\/](antd)/,
          reuseExistingChunk: true,
        },
      }

清理无用的css

有风险,暂且不用

组件懒加载,样式懒加载

  1. 提取第三方包和公共模块
  2. react 再使用 lazy和Suspense即可做到组件懒加载
  3. 如何懒加载css呢,看下方
import React, { lazy, Suspense, useState } from 'react'
const LazyDemo = lazy(() => import('@/components/LazyDemo')) // 使用import语法配合react的Lazy动态引入资源  
export default function App() {
  const [show, setShow] = useState(false)

  // 点击事件中动态引入css, 设置show为true  
  const onClick = () => {
    import("./app.css")
    setShow(true)
  }

  return (
    <>
      <h2 onClick={onClick}>展示</h2>
      {show && <Suspense fallback={<span>加载中</span>}><LazyDemo /></Suspense>}
    </>
  )
}

预加载

懒加载提升首屏渲染速度, 但是加载资源时有请求资源的延时, 如果资源比较大会出现延迟卡顿现象,可以借助link标签的rel属性prefetchpreload,link标签除了加载css之外也可以加载js资源,设置rel属性可以规定link提前加载资源,但是加载资源后不执行,等用到了再执行。

**rel的属性值**

  • **preload**是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源。
  • **prefetch**是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源,会在空闲时加载。

对于当前页面很有必要的资源使用 preload ,对于可能在将来的页面中使用的资源使用 prefetch
webpack v4.6.0+ 增加了对预获取和预加载的支持,使用方式也比较简单,在import引入动态资源时使用webpack的魔法注释;
语法:

// 单个目标
import(
  /* webpackChunkName: "my-chunk-name" */ // 资源打包后的文件chunkname
  /* webpackPrefetch: true */ // 开启prefetch预加载
  /* webpackPreload: true */ // 开启preload预获取
  './module'
);

详细代码测试
测试一下,在src/components目录下新建PreloadDemo.tsx, PreFetchDemo.tsx

// src/components/PreloadDemo.tsx
import React from "react";
function PreloadDemo() {
  return <h3>我是PreloadDemo组件</h3>
}
export default PreloadDemo

// src/components/PreFetchDemo.tsx
import React from "react";
function PreFetchDemo() {
  return <h3>我是PreFetchDemo组件</h3>
}
export default PreFetchDemo

修改App.tsx

import React, { lazy, Suspense, useState } from 'react'

const PreFetchDemo = lazy(() => import(
  /* webpackChunkName: "PreFetchDemo" */
  /*webpackPrefetch: true*/
  '@/components/PreFetchDemo'
))

const PreloadDemo = lazy(() => import(
  /* webpackChunkName: "PreloadDemo" */
  /*webpackPreload: true*/
  '@/components/PreloadDemo'
  ))

function App() {
  const [ show, setShow ] = useState(false)
  
  return (
    <>
      <h3 onClick={() => setShow(true)}>预加载:点击后加载{show ? 1 : 0}</h3>
      { show && (
        <>
          <Suspense fallback={null}><PreloadDemo /></Suspense>
          <Suspense fallback={null}><PreFetchDemo /></Suspense>
        </>
       )}
    </>
  )
}
export default App

结果

  1. 点击显示按钮前,只看到了提前加载prefetch文件,没有看到preload
  2. 使用lazy,点击显示后回去加载js文件

image.png

开启gzip压缩

  1. 可压缩掉70%的体积
  2. 其他知识:nginx可开启gzip压缩,但只在nginx开,每次请求时都对资源进行压缩,压缩需要时间和占用服务器cpu资源,更好的方式是前端在打包的时候直接生成gzip资源,服务器接收到请求,可以直接把对应压缩好的gzip文件返回给浏览器,节省时间和cpu。
  1. 装包 pnpm i compression-webpack-plugin -D
  2. 配置 webpack.prod.js
const CompressionPlugin = require('compression-webpack-plugin')

module.exports = merge(base, {
  plugins: [
    // 开启gzip压缩
    new CompressionPlugin({
      test: /.(js|css)$/, // 只生成css,js压缩文件
      filename: '[path][base].gz', // 文件命名
      algorithm: 'gzip', // 压缩格式,默认是gzip
      test: /.(js|css)$/, // 只生成css,js压缩文件
      threshold: 10240, // 只有大小大于该值的资源会被处理。默认值是 10k
      minRatio: 0.8 // 压缩率,默认值是 0.8
    })
  ]
})

合理配置打包文件hash

  1. webpack的hash有3种类
    1. hash: 跟整个项目相关,所有文件共用一个hash。一个文件改动,所有文件的hash都会动。
    2. chunkhash: 影响范围:文件+依赖,依赖、文件变,则对应都变。-小连坐。
    3. contenthash: 只与文件自身有关。
  2. js-chunkhash
  3. css和图片-contenthash

按需加载

  1. 默认已按需加载,antd只用button 37k,多用几个到100+k
  2. lodash: 使用lodash-es来代替loadsh,只用cloneDeep, 打包后只有13k。

四、项目规范

可参考之前打文章。

五、进阶

区分开发、测试、线上环境

简洁版 - 用于小项目

crossEnv + DefinePlugin,就可以在代码中使用

  1. 先装包pnpm i cross-env dotenv-webpack -D
  2. 在package.json 的命令中 添加 env变量
"scripts": {
  "start": "cross-env NODE_ENV=development webpack serve -c ./config/webpack.dev.js",
  "start:test": "cross-env NODE_ENV=test webpack serve -c ./config/webpack.dev.js",
  "start:prod": "cross-env NODE_ENV=production webpack serve  -c ./config/webpack.dev.js",
  "build:test": "cross-env NODE_ENV=test webpack -c ./config/webpack.prod.js",
  "build:prod": "cross-env NODE_ENV=production webpack -c ./config/webpack.prod.js",
}
  1. 在webpack.base.js中添加 DefinePlugin,注意Json中的 process.env.xxx,和命令中的名字保持一致
const { DefinePlugin } = require('webpack');

module.exports = {
  plugins: [
    new DefinePlugin({ 
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) 
    }),
  ]
}
  1. 使用,随便找个组件测试
console.log('==http:NODE_ENV,', process.env.NODE_ENV);

使用dotenv配置文件 - 大项目

dotenv-webpack 整合了dotenv和DefinePlugin,所以不再DefinePlugin。

  1. 装包pnpm i dotenv-webpack -D
  2. 在webpack.base.js中添加 Dotenv
const Dotenv = require('dotenv-webpack');

module.exports = {
  plugins: [
    new Dotenv({ path: path.resolve(__dirname, '..', `.env.${process.env.NODE_ENV}`) }),
  ]
}
  1. env文件
// .env.development
REACT_APP_ENV=development
REACT_APP_BASEURL=/api

// .env.test
REACT_APP_ENV=test
REACT_APP_BASEURL=/api/test

// .env.production
REACT_APP_ENV=production
REACT_APP_BASEURL=/api/production

六、问题

css_module不生效

  1. 版本问题,将css-loader,从v7 => “^5.2.7”
  2. 普通的css又不生效, 普通less和module.less分开处理 - 参考cra

image.png

二级页面不展示

背景:刷新页面,空白
解决方案:在webpack.base的output添加 publicPath: ‘/’, 即可

image.png

❌如何解决process报错的问题

  1. 背景:组件中单独打印process会报错

console.log('process: ', process); // 报错
** console.log(‘====http:NODE_ENV,’, process.env.NODE_ENV); // 不报错,应为用了dotenv**
console.log(‘process.ENV,’, process.env.NODE_ENV); // 不报错

image.png

注意版本的坑

  1. 处理跨域:webpack-dev-server 需要降级 “4.15.1”,
  2. css_module不生效,将css-loader,从v7 => “^5.2.7”

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2072077.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

调用股票网站接口读取大A数据——个股资金流入趋势

以某股票为例&#xff0c;调用自定义的一个类&#xff0c;读取数据。 class BigAData:# 获取资金流向数据def get_money_flow(self, stock_code, page1, num20, sortopendate, asc0):该函数通过股票代码从新浪财经API获取资金流向数据。参数包括股票代码、页数、每页数量、排序…

Elasticsearch安装 Kibana安装

安装Elasticsearch 一、拉取镜像或者上传 docker pull Elasticsearch 二、将上传的镜像导入(在仓库拉取的这一步跳过) docker load -i es.tar docker load -i 三、创建容器 1.Elasticsearch 注意修改到自己的网络&#xff08;第八行&#xff09; docker run -d \--nam…

创意无限,思维升级:2024年思维导图软件新趋势与精选推荐

如何高效地整理思绪、规划项目、乃至进行知识管理&#xff0c;成为了每个人都需要面对的重要课题。xmind思维导图这一强大的思维工具&#xff0c;可以辅助我们对只是进行梳理&#xff0c;这次我就展示一下这类思维导图工具有哪些。 1福晰思维导图 链接一下&#xff1a;https:…

HashMap 的实现原理

说一下 HashMap 的实现原理&#xff1f; JDK1.7 HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元&#xff0c;每一个Entry包含一个key-value键值对。&#xff08;其实所谓Map其实就是保存了两个对象之间的映射关系的一种集合&#xff09;&#xff0c;其中Key 和…

pycharm 出现库已经安装了,但是无法导入的解决方法

打开File - Setttings &#xff0c; 找到 Project interpreter 找到system interpreter 安装

Golang | Leetcode Golang题解之第372题超级次方

题目&#xff1a; 题解&#xff1a; const mod 1337func pow(x, n int) int {res : 1for ; n > 0; n / 2 {if n&1 > 0 {res res * x % mod}x x * x % mod}return res }func superPow(a int, b []int) int {ans : 1for _, e : range b {ans pow(ans, 10) * pow(a…

【Python】家庭用电数据分析Prophet预测

数据集&#xff1a;Household Electricity Consumption | Kaggle 目录 数据集简介 探索性分析 Prophet预测 Prophet模型 Prophet理念 Prophet优点 数据集简介 240000-household-electricity-consumption-records数据集包含了一个家庭6个月的用电数据&#xff0c;收集于2…

vitepress打包异常 build error

今天给vitepress进行打包发布时出现了一个很奇怪的报错。 然后通过git版本回滚发现是正常发布的&#xff0c;说明环境是没有问题的 那么&#xff0c;就看看到改变了哪些文件。 环境版本 vitepress(^1.3.1) node(v18.19.0)猜测1 是文件的内容里面图片找不到导致的错误。猜测2 是…

Ps:首选项 - 历史记录

Ps菜单&#xff1a;编辑/首选项 Edit/Preferences 快捷键&#xff1a;Ctrl K Photoshop 首选项中的“历史记录” History选项卡允许用户更好地管理Photoshop中的编辑历史&#xff0c;确保在需要时能够回溯操作或提供详细的操作记录。 提示&#xff1a; 默认情况下&#xff0c;…

[数据集][目标检测]电力场景输电线防震锤检测数据集VOC+YOLO格式2721张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2721 标注数量(xml文件个数)&#xff1a;2721 标注数量(txt文件个数)&#xff1a;2721 标注…

Go语言操作文件上传和下载应用教程

Go语言操作文件上传和下载应用教程 我们在使用Go的日常开发中&#xff0c;经常会遇到对文件的处理&#xff0c;例如&#xff1a;上传、下载、读写等&#xff08;详情见Go 文件操作基本方法大全&#xff09;&#xff0c;且我们在实际应用中&#xff0c;基本都是使用框架自带的文…

数据结构(Java实现):链表习题

文章目录 1. 题目列表及链接2. 题目解析及代码2.1 删除链表中等于给定值 val 的所有节点2.2 反转一个单链表2.3 给定一个带有头结点 head 的非空单链表&#xff0c;返回链表的中间结点。如果有两个中间结点&#xff0c;则返回第二个中间结点2.4 输入一个链表&#xff0c;输出该…

Edge浏览器:Github加速插件,让你在国内自由自在的访问Github!

你是否有访问GitHub要么超级慢&#xff0c;要么无法访问的时刻&#xff0c;是不是感觉痛苦不已&#xff1b; 现在给大家分享我解决问题的方法。 点击浏览器右上角的三个点【…】按钮&#xff0c;然后选择【扩展】 选择【管理扩展】 点击【获取 Miscrosoft Edge扩展】 在搜索框…

解决方案:在jupyter notebook环境下安装不了numpy

文章目录 一、现象二、解决方案 一、现象 平台&#xff1a;autodl 镜像&#xff1a;PyTorch 2.0.0 Python 3.8(ubuntu20.04) Cuda 11.8 GPU&#xff1a;RTX 4090(24GB) * 1 CPU&#xff1a;12 vCPU Intel Xeon Platinum 8352V CPU 2.10GHz 内存&#xff1a;90GB 安装numpy环…

推荐两款好用的录屏软件

Ocam oCam 是一款功能强大的屏幕录制软件&#xff0c;主要用于录制电脑屏幕上的活动。它支持多种视频格式&#xff0c;包括 AVI、MP4、FLV、MOV、TS 和 VOB&#xff0c;同时也支持多种音频格式&#xff0c;如 MP3。oCam 可以录制视频并保存为各种视频格式&#xff0c;还可以编辑…

绘剪批量软件——绘剪批量软件

批量软件是一种可以批量处理大量数据或操作的软件。它通常通过自动化的方式&#xff0c;快速高效地完成任务&#xff0c;减少人工操作的时间和工作量。批量软件可以用于数据处理、文件转换、批量重命名、批量下载等各种场景。 绘剪批量软件——绘剪TK批量软件 AIWYZ77 批量软…

前端JS——补充内容

这期是番外篇&#xff0c;主要是补充一下&#xff0c;之前没有说完整的内容。 后面两期太仓促了&#xff0c;一些值得注意的细节没有提到 之前的内容可以点击&#xff1a; JS总结上 JS总结中 JS总结下——DOM操作 JS总结下——事件操作 前面的两篇总结没什么好补充的&…

[Algorithm][综合训练][mari和shiny][重排字符串]详细讲解

目录 1.mari和shiny1.题目链接2.算法原理详解 && 代码实现 2.重排字符串1.题目链接2.算法原理详解 && 代码实现 1.mari和shiny 1.题目链接 mari和shiny 2.算法原理详解 && 代码实现 自己的版本&#xff1a;三层循环暴力枚举 --> 超时 --> 40% …

[项目]-通讯录的实现

前言 各位师傅大家好&#xff0c;我是qmx_07&#xff0c;今天来结合前面所学知识点&#xff0c;写一个能够增删改查&#xff0c;持久化数据的通讯录功能 准备工作 项目 一般会写成多个文件来实现&#xff0c;调用&#xff0c;接口声明&#xff0c;接口实现&#xff0c;这是一…

游戏开发设计模式之组件模式

目录 组件模式在游戏开发中的具体应用案例是什么&#xff1f; 如何在Unity引擎中实现和优化组件模式&#xff1f; 组件模式与其他设计模式&#xff08;如观察者模式、状态模式&#xff09;在游戏开发中的比较优势在哪里&#xff1f; 组件模式 观察者模式 状态模式 综合比…