【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境

目录

1.前言
2.初始化项目
3.配置基础版react+ts环境
4.常用功能配置
5.配置react模块热替换
6.优化构建速度
7.优化构建结果文件
8.总结

全文概览

【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第1张图片

一. 前言

20201010日,webpack 升级至 5 版本到现在已经快两年,webpack5版本优化了很多原有的功能比如tree-shaking优化,也新增了很多新特性,比如联邦模块,具体变动可以看这篇文章阔别两年,webpack 5 正式发布了!。

本文将使用最新的webpack5一步一步从零搭建一个完整的react18+ts开发和打包环境,配置完善的模块热替换以及构建速度构建结果的优化,完整代码已上传到webpack5-react-ts。本文只是配置webpack的,配置代码规范相关的可以看这篇文章搭建react18+vite2+ts+prettier+eslint+lint-staged+husky+stylelint开发环境

二. 初始化项目

在开始webpack配置之前,先手动初始化一个基本的react+ts项目,新建项目文件夹webpack5-react-18, 在项目下执行

npm init -y 

初始化好package.json后,在项目下新增以下所示目录结构和文件

├── build
| ├── webpack.base.js # 公共配置
| ├── webpack.dev.js# 开发环境配置
| └── webpack.prod.js # 打包环境配置
├── public
│ └── index.html # html模板
├── src
| ├── App.tsx 
│ └── index.tsx # react应用入口页面
├── tsconfig.json# ts配置
└── package.json 

安装webpack依赖

npm i webpack webpack-cli -D 

安装react依赖

npm i react react-dom -S 

安装react类型依赖

npm i @types/react @types/react-dom -D 

添加public/index.html内容



webpack5-react-ts

添加tsconfig.json内容

{"compilerOptions": {"target": "ESNext","lib": ["DOM", "DOM.Iterable", "ESNext"],"allowJs": false,"skipLibCheck": false,"esModuleInterop": false,"allowSyntheticDefaultImports": true,"strict": true,"forceConsistentCasingInFileNames": true,"module": "ESNext","moduleResolution": "Node","resolveJsonModule": true,"isolatedModules": true,"noEmit": true,"jsx": "react", // react18这里也可以改成react-jsx},"include": ["./src"]
} 

添加src/App.tsx内容

import React from 'react'

function App() {return 

webpack5-react-ts

} export default App

添加src/index.tsx内容

import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const root = document.getElementById('root');
if(root) {createRoot(root).render()
} 

现在项目业务代码已经添加好了,接下来可以配置webpack的代码了。

三. 配置基础版React+ts环境

2.1. webpack公共配置

修改webpack.base.js

1. 配置入口文件

// webpack.base.js
const path = require('path')

module.exports = {entry: path.join(__dirname, '../src/index.tsx'), // 入口文件
} 

2. 配置出口文件

// webpack.base.js
const path = require('path')

module.exports = {// ...// 打包文件出口output: {filename: 'static/js/[name].js', // 每个输出js的名称path: path.join(__dirname, '../dist'), // 打包结果输出路径clean: true, // webpack4需要配置clean-webpack-plugin来删除dist文件,webpack5内置了publicPath: '/' // 打包后文件的公共前缀路径},
} 

3. 配置loader解析ts和jsx

由于webpack默认只能识别js文件,不能识别jsx语法,需要配置loader的预设预设 @babel/preset-typescript 来先ts语法转换为 js 语法,再借助预设 @babel/preset-react 来识别jsx语法。

安装babel核心模块和babel预设

npm i babel-loader @babel/core @babel/preset-react @babel/preset-typescript -D 

webpack.base.js添加module.rules配置

// webpack.base.js
module.exports = {// ...module: {rules: [{test: /.(ts|tsx)$/, // 匹配.ts, tsx文件use: {loader: 'babel-loader',options: {// 预设执行顺序由右往左,所以先处理ts,再处理jsxpresets: ['@babel/preset-react','@babel/preset-typescript']}}}]}
} 

4. 配置extensions

extensionswebpackresolve解析配置下的选项,在引入模块时不带文件后缀时,会来该配置数组里面依次添加后缀查找文件,因为ts不支持引入以 .ts, tsx为后缀的文件,所以要在extensions中配置,而第三方库里面很多引入js文件没有带后缀,所以也要配置下js

修改webpack.base.js,注意把高频出现的文件后缀放在前面

// webpack.base.js
module.exports = {// ...resolve: {extensions: ['.js', '.tsx', '.ts'],}
} 

这里只配置js, tsxts,其他文件引入都要求带后缀,可以提升构建速度。

4. 添加html-webpack-plugin插件

webpack需要把最终构建好的静态资源都引入到一个html文件中,这样才能在浏览器中运行,html-webpack-plugin就是来做这件事情的,安装依赖:

npm i html-webpack-plugin -D 

因为该插件在开发和构建打包模式都会用到,所以还是放在公共配置webpack.base.js里面

// webpack.base.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {// ...plugins: [new HtmlWebpackPlugin({template: path.resolve(__dirname, '../public/index.html'), // 模板取定义root节点的模板inject: true, // 自动注入静态资源})]
} 

到这里一个最基础的react基本公共配置就已经配置好了,需要在此基础上分别配置开发环境和打包环境了。

2.2. webpack开发环境配置

1. 安装 webpack-dev-server

开发环境配置代码在webpack.dev.js中,需要借助 webpack-dev-server在开发环境启动服务器来辅助开发,还需要依赖webpack-merge来合并基本配置,安装依赖:

npm i webpack-dev-server webpack-merge -D 

修改webpack.dev.js代码, 合并公共配置,并添加开发模式配置

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

// 合并公共配置,并添加开发环境配置
module.exports = merge(baseConfig, {mode: 'development', // 开发模式,打包更加快速,省了代码优化步骤devtool: 'eval-cheap-module-source-map', // 源码调试模式,后面会讲devServer: {port: 3000, // 服务端口号compress: false, // gzip压缩,开发环境不开启,提升热更新速度hot: true, // 开启热更新,后面会讲react模块热替换具体配置historyApiFallback: true, // 解决history路由404问题static: {directory: path.join(__dirname, "../public"), //托管静态资源public文件夹}}
}) 

2. package.json添加dev脚本

package.jsonscripts中添加

// package.json
"scripts": {"dev": "webpack-dev-server -c build/webpack.dev.js"
}, 

执行npm run dev,就能看到项目已经启动起来了,访问http://localhost:3000/,就可以看到项目界面,具体完善的react模块热替换在下面会讲到。

2.3. webpack打包环境配置

1. 修改webpack.prod.js代码

// webpack.prod.js

const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base.js')
module.exports = merge(baseConfig, {mode: 'production', // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
}) 

2. package.json添加build打包命令脚本

package.jsonscripts中添加build打包命令

"scripts": {"dev": "webpack-dev-server -c build/webpack.dev.js","build": "webpack -c build/webpack.prod.js"
}, 

执行npm run build,最终打包在dist文件中, 打包结果:

dist├── static
| ├── js
| ├── main.js
├── index.html 

3. 浏览器查看打包结果

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

npm i serve -g 

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

到现在一个基础的支持reacttswebpack5就配置好了,但只有这些功能是远远不够的,还需要继续添加其他配置。

四. 基础功能配置

4.1 配置环境变量

环境变量按作用来分分两种

1.区分是开发模式还是打包构建模式
2.区分项目业务环境,开发/测试/预测/正式环境

区分开发模式还是打包构建模式可以用process.env.NODE_ENV,因为很多第三方包里面判断都是采用的这个环境变量。

区分项目接口环境可以自定义一个环境变量process.env.BASE_ENV,设置环境变量可以借助cross-env和webpack.DefinePlugin来设置。

  • cross-env:兼容各系统的设置环境变量的包
  • webpack.DefinePluginwebpack内置的插件,可以为业务代码注入环境变量

安装cross-env

npm i cross-env -D 

修改package.jsonscripts脚本字段,删除原先的devbuild,改为

"scripts": {"dev:dev": "cross-env NODE_ENV=development BASE_ENV=development webpack-dev-server -c build/webpack.dev.js","dev:test": "cross-env NODE_ENV=development BASE_ENV=test webpack-dev-server -c build/webpack.dev.js","dev:pre": "cross-env NODE_ENV=development BASE_ENV=pre webpack-dev-server -c build/webpack.dev.js","dev:prod": "cross-env NODE_ENV=development BASE_ENV=production webpack-dev-server -c build/webpack.dev.js","build:dev": "cross-env NODE_ENV=production BASE_ENV=development webpack -c build/webpack.prod.js","build:test": "cross-env NODE_ENV=production BASE_ENV=test webpack -c build/webpack.prod.js","build:pre": "cross-env NODE_ENV=production BASE_ENV=pre webpack -c build/webpack.prod.js","build:prod": "cross-env NODE_ENV=production BASE_ENV=production webpack -c build/webpack.prod.js",}, 

dev开头是开发模式,build开头是打包模式,冒号后面对应的dev/test/pre/prod是对应的业务环境的开发/测试/预测/正式环境。

process.env.NODE_ENV环境变量webpack会自动根据设置的mode字段来给业务代码注入对应的developmentprodction,这里在命令中再次设置环境变量NODE_ENV是为了在webpackbabel的配置文件中访问到。

webpack.base.js中打印一下设置的环境变量

// webpack.base.js
// ...
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV) 

执行npm run build:dev,可以看到打印的信息

// NODE_ENV production
// BASE_ENV development 

当前是打包模式,业务环境是开发环境,这里需要把process.env.BASE_ENV注入到业务代码里面,就可以通过该环境变量设置对应环境的接口地址和其他数据,要借助webpack.DefinePlugin插件。

修改webpack.base.js

// webpack.base.js
// ...
const webpack = require('webpack')
module.export = {// ...plugins: [// ...new webpack.DefinePlugin({'process.env.BASE_ENV': JSON.stringify(process.env.BASE_ENV)})]
} 

配置后会把值注入到业务代码里面去,webpack解析代码匹配到process.env.BASE_ENV,就会设置到对应的值。测试一下,在src/index.tsx打印一下两个环境变量

// src/index.tsx
// ...
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV) 

执行npm run dev:test,可以在浏览器控制台看到打印的信息

// NODE_ENV development
// BASE_ENV test 

当前是开发模式,业务环境是测试环境。

4.2 处理css和less文件

src下新增app.css

h2 {color: red;transform: translateY(100px);
} 

src/App.tsx中引入app.css

import React from 'react'
import './app.css'

function App() {return 

webpack5-rea11ct-ts

} export default App

执行打包命令npm run build:dev,会发现有报错, 因为webpack默认只认识js,是不识别css文件的,需要使用loader来解析css, 安装依赖

npm i style-loader css-loader -D 
  • style-loader: 把解析后的css代码从js中抽离,放到头部的style标签中(在运行时做的)
  • css-loader: 解析css文件代码

因为解析css的配置开发和打包环境都会用到,所以加在公共配置webpack.base.js

// webpack.base.js
// ...
module.exports = {// ...module: { rules: [// ...{test: /.css$/, //匹配 css 文件use: ['style-loader','css-loader']}]},// ...
} 

上面提到过,loader执行顺序是从右往左,从下往上的,匹配到css文件后先用css-loader解析css, 最后借助style-loadercss插入到头部style标签中。

配置完成后再npm run build:dev打包,借助serve -s dist启动后在浏览器查看,可以看到样式生效了。

【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第2张图片

4.3 支持less或scss

项目开发中,为了更好的提升开发体验,一般会使用css超集less或者scss,对于这些超集也需要对应的loader来识别解析。以less为例,需要安装依赖:

npm i less-loader less -D 
  • less-loader: 解析less文件代码,把less编译为css
  • less: less核心

实现支持less也很简单,只需要在rules中添加less文件解析,遇到less文件,使用less-loader解析为css,再进行css解析流程,修改webpack.base.js

// webpack.base.js
module.exports = {// ...module: {// ...rules: [// ...{test: /.(css|less)$/, //匹配 css和less 文件use: ['style-loader','css-loader', 'less-loader']}]},// ...
} 

测试一下,新增src/app.less

#root {h2 {font-size: 20px;}
} 

App.tsx中引入app.less,执行npm run build:dev打包,借助serve -s dist启动项目,可以看到less文件编写的样式编译css后也插入到style标签了了。

【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第3张图片

4.4 处理css3前缀兼容

虽然css3现在浏览器支持率已经很高了, 但有时候需要兼容一些低版本浏览器,需要给css3加前缀,可以借助插件来自动加前缀, postcss-loader就是来给css3加浏览器前缀的,安装依赖:

npm i postcss-loader autoprefixer -D 
  • postcss-loader:处理css时自动加前缀
  • autoprefixer:决定添加哪些浏览器前缀到css

修改webpack.base.js, 在解析cssless的规则中添加配置

module.exports = {// ...module: { rules: [// ...{test: /.(css|less)$/, //匹配 css和less 文件use: ['style-loader','css-loader',// 新增{loader: 'postcss-loader',options: {postcssOptions: {plugins: ['autoprefixer']}}},'less-loader']}]},// ...
} 

配置完成后,需要有一份要兼容浏览器的清单,让postcss-loader知道要加哪些浏览器的前缀,在根目录创建 .browserslistrc文件

IE 9 # 兼容IE 9
chrome 35 # 兼容chrome 35 

以兼容到ie9chrome35版本为例,配置好后,执行npm run build:dev打包,可以看到打包后的css文件已经加上了ie和谷歌内核的前缀

【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第4张图片

上面可以看到解析cssless有很多重复配置,可以进行提取postcss-loader配置优化一下

postcss.config.jspostcss-loader的配置文件,会自动读取配置,根目录新建postcss.config.js

module.exports = {plugins: ['autoprefixer']
} 

修改webpack.base.js, 取消postcss-loaderoptions配置

// webpack.base.js
// ...
module.exports = {// ...module: { rules: [// ...{test: /.(css|less)$/, //匹配 css和less 文件use: ['style-loader','css-loader','postcss-loader','less-loader']},]},// ...
} 

提取postcss-loader配置后,再次打包,可以看到依然可以解析css, less文件, css3对应前缀依然存在。

4.5 babel预设处理js兼容

现在js不断新增很多方便好用的标准语法来方便开发,甚至还有非标准语法比如装饰器,都极大的提升了代码可读性和开发效率。但前者标准语法很多低版本浏览器不支持,后者非标准语法所有的浏览器都不支持。需要把最新的标准语法转换为低版本语法,把非标准语法转换为标准语法才能让浏览器识别解析,而babel就是来做这件事的,这里只讲配置,更详细的可以看Babel 那些事儿。

安装依赖

npm i babel-loader @babel/core @babel/preset-env core-js -D 
  • babel-loader: 使用 babel 加载最新js代码并将其转换为 ES5(上面已经安装过)
  • @babel/corer: babel 编译的核心包
  • @babel/preset-env: babel 编译的预设,可以转换目前最新的js标准语法
  • core-js: 使用低版本js语法模拟高版本的库,也就是垫片

修改webpack.base.js

// webpack.base.js
module.exports = {// ...module: {rules: [{test: /.(ts|tsx)$/,use: {loader: 'babel-loader',options: {// 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法presets: [["@babel/preset-env",{// 设置兼容目标浏览器版本,这里可以不写,babel-loader会自动寻找上面配置好的文件.browserslistrc// "targets": {//"chrome": 35,//"ie": 9// }, "useBuiltIns": "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加 "corejs": 3, // 配置使用core-js低版本}],'@babel/preset-react','@babel/preset-typescript']}}}]}
} 

此时再打包就会把语法转换为对应浏览器兼容的语法了。

为了避免webpack配置文件过于庞大,可以把babel-loader的配置抽离出来, 新建babel.config.js文件,使用js作为配置文件,是因为可以访问到process.env.NODE_ENV环境变量来区分是开发还是打包模式。

// babel.config.js
module.exports = {// 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法"presets": [["@babel/preset-env",{// 设置兼容目标浏览器版本,这里可以不写,babel-loader会自动寻找上面配置好的文件.browserslistrc// "targets": {//"chrome": 35,//"ie": 9// },"useBuiltIns": "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加"corejs": 3 // 配置使用core-js使用的版本}],"@babel/preset-react","@babel/preset-typescript"]
} 

移除webpack.base.jsbabel-loaderoptions配置

// webpack.base.js
module.exports = {// ...module: {rules: [{test: /.(ts|tsx)$/,use: 'babel-loader'},// 如果node_moduels中也有要处理的语法,可以把js|jsx文件配置加上// {//test: /.(js|jsx)$/,//use: 'babel-loader'// }// ...]}
} 

4.6 babel处理js非标准语法

现在react主流开发都是函数组件和react-hooks,但有时也会用类组件,可以用装饰器简化代码。

新增src/components/Class.tsx组件, 在App.tsx中引入该组件使用

import React, { PureComponent } from "react";

// 装饰器为,组件添加age属性
function addAge(Target: Function) {Target.prototype.age = 111
}
// 使用装饰圈
@addAge
class Class extends PureComponent {age?: numberrender() {return (

我是类组件---{this.age}

)} } export default Class

需要开启一下ts装饰器支持,修改tsconfig.json文件

// tsconfig.json
{"compilerOptions": {// ...// 开启装饰器使用"experimentalDecorators": true}
} 

上面Class组件代码中使用了装饰器,目前js标准语法是不支持的,现在运行或者打包会报错,不识别装饰器语法,需要借助babel-loader插件,安装依赖

npm i @babel/plugin-proposal-decorators -D 

babel.config.js中添加插件

module.exports = { // ..."plugins": [["@babel/plugin-proposal-decorators", { "legacy": true }]]
} 

现在项目就支持装饰器了。

4.7 复制public文件夹

一般public文件夹都会放一些静态资源,可以直接根据绝对路径引入,比如图片,css,js文件等,不需要webpack进行解析,只需要打包的时候把public下内容复制到构建出口文件夹中,可以借助copy-webpack-plugin插件,安装依赖

npm i copy-webpack-plugin -D 

开发环境已经在devServer中配置了static托管了public文件夹,在开发环境使用绝对路径可以访问到public下的文件,但打包构建时不做处理会访问不到,所以现在需要在打包配置文件webpack.prod.js中新增copy插件配置。

// webpack.prod.js
// ..
const path = require('path')
const CopyPlugin = require('copy-webpack-plugin');
module.exports = merge(baseConfig, {mode: 'production',plugins: [// 复制文件插件new CopyPlugin({patterns: [{from: path.resolve(__dirname, '../public'), // 复制public下文件to: path.resolve(__dirname, '../dist'), // 复制到dist目录中filter: source => {return !source.includes('index.html') // 忽略index.html}},],}),]
}) 

在上面的配置中,忽略了index.html,因为html-webpack-plugin会以public下的index.html为模板生成一个index.htmldist文件下,所以不需要再复制该文件了。

测试一下,在public中新增一个favicon.ico图标文件,在index.html中引入



<webpack5-react-ts</title" style="margin: auto" />
</head>
<body><!-- 容器节点 --><div id="root"></div>
</body>
</html> 
</code></pre> 
  <p>再执行<strong>npm run build:dev</strong>打包,就可以看到<strong>public</strong>下的<strong>favicon.ico</strong>图标文件被复制到<strong>dist</strong>文件中了。</p> <a href="http://img.e-com-net.com/image/info8/637fdc85d2d042cea0ab9afcbed397eb.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/637fdc85d2d042cea0ab9afcbed397eb.jpg" width="650" height="213" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第5张图片" style="border:1px solid black;"></a> 
  <h3>4.8 处理图片文件</h3> 
  <p>对于图片文件,<strong>webpack4</strong>使用<strong>file-loader</strong>和<strong>url-loader</strong>来处理的,但<strong>webpack5</strong>不使用这两个<strong>loader</strong>了,而是采用自带的<strong>asset-module</strong>来处理</p> 
  <p>修改<strong>webpack.base.js</strong>,添加图片解析配置</p> 
  <pre><code>module.exports = {module: {rules: [// ...{test:/.(png|jpg|jpeg|gif|svg)$/, // 匹配图片文件type: "asset", // type选择assetparser: {dataUrlCondition: {maxSize: 10 * 1024, // 小于10kb转base64位}},generator:{ filename:'static/images/[name][ext]', // 文件输出目录和命名},},]}
} 
</code></pre> 
  <p>测试一下,准备一张小于10kb的图片和大于10kb的图片,放在<strong>src/assets/imgs</strong>目录下, 修改<strong>App.tsx</strong>:</p> 
  <pre><code>import React from 'react'
import smallImg from './assets/imgs/5kb.png'
import bigImg from './assets/imgs/22kb.png'
import './app.css'
import './app.less'

function App() {return (<><img src={smallImg} alt="小于10kb的图片" /><img src={bigImg} alt="大于于10kb的图片" /></>)
}
export default App 
</code></pre> 
  <blockquote> 
   <p>这个时候在引入图片的地方会报:<strong>找不到模块“./assets/imgs/22kb.png”或其相应的类型声明</strong>,需要添加一个图片的声明文件</p> 
  </blockquote> 
  <p>新增<strong>src/images.d.ts</strong>文件,添加内容</p> 
  <pre><code>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' 
</code></pre> 
  <p>添加图片声明文件后,就可以正常引入图片了, 然后执行<strong>npm run build:dev</strong>打包,借助<strong>serve -s dist</strong>查看效果,可以看到可以正常解析图片了,并且小于<strong>10kb</strong>的图片被转成了<strong>base64</strong>位格式的。</p> <a href="http://img.e-com-net.com/image/info8/f1b44abcc57a4d66a94f17ef9f9eaa07.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/f1b44abcc57a4d66a94f17ef9f9eaa07.jpg" width="650" height="224" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第6张图片" style="border:1px solid black;"></a> 
  <p><strong>css</strong>中的背景图片一样也可以解析,修改<strong>app.tsx</strong>。</p> 
  <pre><code>import React from 'react'
import smallImg from './assets/imgs/5kb.png'
import bigImg from './assets/imgs/22kb.png'
import './app.css'
import './app.less'

function App() {return (<><img src={smallImg} alt="小于10kb的图片" /><img src={bigImg} alt="大于于10kb的图片" /><div className='smallImg'></div> {/* 小图片背景容器 */}<div className='bigImg'></div> {/* 大图片背景容器 */}</>)
}
export default App 
</code></pre> 
  <p>修改<strong>app.less</strong></p> 
  <pre><code>// app.less
#root {.smallImg {width: 69px;height: 75px;background: url('./assets/imgs/5kb.png') no-repeat;}.bigImg {width: 232px;height: 154px;background: url('./assets/imgs/22kb.png') no-repeat;}
} 
</code></pre> 
  <p>可以看到背景图片也一样可以识别,小于<strong>10kb</strong>转为<strong>base64</strong>位。</p> <a href="http://img.e-com-net.com/image/info8/5287f204a52b4fe49eb61099af4aef6e.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/5287f204a52b4fe49eb61099af4aef6e.jpg" width="650" height="304" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第7张图片" style="border:1px solid black;"></a> 
  <h3>4.9 处理字体和媒体文件</h3> 
  <p>字体文件和媒体文件这两种资源处理方式和处理图片是一样的,只需要把匹配的路径和打包后放置的路径修改一下就可以了。修改<strong>webpack.base.js</strong>文件:</p> 
  <pre><code>// webpack.base.js
module.exports = {module: {rules: [// ...{test:/.(woff2?|eot|ttf|otf)$/, // 匹配字体图标文件type: "asset", // type选择assetparser: {dataUrlCondition: {maxSize: 10 * 1024, // 小于10kb转base64位}},generator:{ filename:'static/fonts/[name][ext]', // 文件输出目录和命名},},{test:/.(mp4|webm|ogg|mp3|wav|flac|aac)$/, // 匹配媒体文件type: "asset", // type选择assetparser: {dataUrlCondition: {maxSize: 10 * 1024, // 小于10kb转base64位}},generator:{ filename:'static/media/[name][ext]', // 文件输出目录和命名},},]}
} 
</code></pre> 
  <h2>五. 配置react模块热更新</h2> 
  <p>热更新上面已经在<strong>devServer</strong>中配置<strong>hot</strong>为<strong>true</strong>, 在<strong>webpack4</strong>中,还需要在插件中添加了<strong>HotModuleReplacementPlugin</strong>,在<strong>webpack5</strong>中,只要<strong>devServer.hot</strong>为<strong>true</strong>了,该插件就已经内置了。</p> 
  <p>现在开发模式下修改<strong>css</strong>和<strong>less</strong>文件,页面样式可以在不刷新浏览器的情况实时生效,因为此时样式都在<strong>style</strong>标签里面,<strong>style-loader</strong>做了替换样式的热替换功能。但是修改<strong>App.tsx</strong>,浏览器会自动刷新后再显示修改后的内容,但我们想要的不是刷新浏览器,而是在不需要刷新浏览器的前提下模块热更新,并且能够保留<strong>react</strong>组件的状态。</p> 
  <p>可以借助@pmmmwh/react-refresh-webpack-plugin插件来实现,该插件又依赖于react-refresh, 安装依赖:</p> 
  <pre><code>npm i @pmmmwh/react-refresh-webpack-plugin react-refresh -D 
</code></pre> 
  <p>配置<strong>react</strong>热更新插件,修改<strong>webpack.dev.js</strong></p> 
  <pre><code>// webpack.dev.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = merge(baseConfig, {// ...plugins: [new ReactRefreshWebpackPlugin(), // 添加热更新插件]
}) 
</code></pre> 
  <p>为<strong>babel-loader</strong>配置<strong>react-refesh</strong>刷新插件,修改<strong>babel.config.js</strong>文件</p> 
  <pre><code>const isDEV = process.env.NODE_ENV === 'development' // 是否是开发模式
module.exports = {// ..."plugins": [isDEV && require.resolve('react-refresh/babel'), // 如果是开发模式,就启动react热更新插件// ...].filter(Boolean) // 过滤空值
} 
</code></pre> 
  <p>测试一下,修改<strong>App.tsx</strong>代码</p> 
  <pre><code>import React, { useState } from 'react'

function App() {const [ count, setCounts ] = useState('')const onChange = (e: any) => {setCounts(e.target.value)}return (<><h2>webpack5+react+ts</h2><p>受控组件</p><input type="text" value={count} onChange={onChange} /><br /><p>非受控组件</p><input type="text" /></>)
}
export default App 
</code></pre> 
  <p>在两个输入框分别输入内容后,修改<strong>App.tsx</strong>中<strong>h2</strong>标签的文本,会发现在不刷新浏览器的情况下,页面内容进行了热更新,并且<strong>react</strong>组件状态也会保留。</p> <a href="http://img.e-com-net.com/image/info8/2fc7c13c635d4fbe816c25a987e60ad6.png" target="_blank"><img src="http://img.e-com-net.com/image/info8/2fc7c13c635d4fbe816c25a987e60ad6.png" width="513" height="264" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第8张图片" style="border:1px solid black;"></a> <a href="http://img.e-com-net.com/image/info8/c63510528aa4473faefa6a1645f14d1a.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/c63510528aa4473faefa6a1645f14d1a.jpg" width="568" height="277" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第9张图片" style="border:1px solid black;"></a> 
  <blockquote> 
   <p>新增或者删除页面<strong>hooks</strong>时,热更新时组件状态不会保留。</p> 
  </blockquote> 
  <h2>六. 优化构建速度</h2> 
  <h3>6.1 构建耗时分析</h3> 
  <p>当进行优化的时候,肯定要先知道时间都花费在哪些步骤上了,而speed-measure-webpack-plugin插件可以帮我们做到,安装依赖:</p> 
  <pre><code>npm i speed-measure-webpack-plugin -D 
</code></pre> 
  <p>使用的时候为了不影响到正常的开发/打包模式,我们选择新建一个配置文件,新增<strong>webpack</strong>构建分析配置文件<strong>build/webpack.analy.js</strong></p> 
  <pre><code>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, {

})) 
</code></pre> 
  <p>修改<strong>package.json</strong>添加启动<strong>webpack</strong>打包分析脚本命令,在<strong>scripts</strong>新增:</p> 
  <pre><code>{// ..."scripts": {// ..."build:analy": "cross-env NODE_ENV=production BASE_ENV=production webpack -c build/webpack.analy.js"}// ...
} 
</code></pre> 
  <p>执行<strong>npm run build:analy</strong>命令</p> <a href="http://img.e-com-net.com/image/info8/92849a1467d549fb8145a00008d19ce9.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/92849a1467d549fb8145a00008d19ce9.jpg" width="650" height="532" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第10张图片" style="border:1px solid black;"></a> 
  <p>可以在图中看到各<strong>plugin</strong>和<strong>loader</strong>的耗时时间,现在因为项目内容比较少,所以耗时都比较少,在真正的项目中可以通过这个来分析打包时间花费在什么地方,然后来针对性的优化。</p> 
  <h3>6.2 开启持久化存储缓存</h3> 
  <p>在<strong>webpack5</strong>之前做缓存是使用<strong>babel-loader</strong>缓存解决<strong>js</strong>的解析结果,<strong>cache-loader</strong>缓存<strong>css</strong>等资源的解析结果,还有模块缓存插件<strong>hard-source-webpack-plugin</strong>,配置好缓存后第二次打包,通过对文件做哈希对比来验证文件前后是否一致,如果一致则采用上一次的缓存,可以极大地节省时间。</p> 
  <p><strong>webpack5</strong> 较于 <strong>webpack4</strong>,新增了持久化缓存、改进缓存算法等优化,通过配置 webpack 持久化缓存,来缓存生成的 <strong>webpack</strong> 模块和 <strong>chunk</strong>,改善下一次打包的构建速度,可提速 <strong>90%</strong> 左右,配置也简单,修改<strong>webpack.base.js</strong></p> 
  <pre><code>// webpack.base.js
// ...
module.exports = {// ...cache: {type: 'filesystem', // 使用文件缓存},
} 
</code></pre> 
  <p>当前文章代码的测试结果</p> 
  <table> 
   <thead> 
    <tr> 
     <th>模式</th> 
     <th>第一次耗时</th> 
     <th>第二次耗时</th> 
    </tr> 
   </thead> 
   <tbody> 
    <tr> 
     <td>启动开发模式</td> 
     <td>2869毫秒</td> 
     <td>687毫秒</td> 
    </tr> 
    <tr> 
     <td>启动打包模式</td> 
     <td>5455毫秒</td> 
     <td>552毫秒</td> 
    </tr> 
   </tbody> 
  </table> 
  <p>通过开启<strong>webpack5</strong>持久化存储缓存,再次打包的时间提升了<strong>90%</strong>。</p> <a href="http://img.e-com-net.com/image/info8/a3dfc01ca9cd4b87bdbcf0d58f806e7f.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/a3dfc01ca9cd4b87bdbcf0d58f806e7f.jpg" width="650" height="267" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第11张图片" style="border:1px solid black;"></a> 
  <p>缓存的存储位置在<strong>node_modules/.cache/webpack</strong>,里面又区分了<strong>development</strong>和<strong>production</strong>缓存</p> <a href="http://img.e-com-net.com/image/info8/646a592a0a604f91ae708de01abebe0d.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/646a592a0a604f91ae708de01abebe0d.jpg" width="650" height="263" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第12张图片" style="border:1px solid black;"></a> 
  <h3>6.3 开启多线程loader</h3> 
  <p><strong>webpack</strong>的<strong>loader</strong>默认在单线程执行,现代电脑一般都有多核<strong>cpu</strong>,可以借助多核<strong>cpu</strong>开启多线程<strong>loader</strong>解析,可以极大地提升<strong>loader</strong>解析的速度,thread-loader就是用来开启多进程解析<strong>loader</strong>的,安装依赖</p> 
  <pre><code>npm i thread-loader -D 
</code></pre> 
  <p>使用时,需将此 <strong>loader</strong> 放置在其他 <strong>loader</strong> 之前。放置在此 <strong>loader</strong> 之后的 <strong>loader</strong> 会在一个独立的 <strong>worker</strong> 池中运行。</p> 
  <p>修改<strong>webpack.base.js</strong></p> 
  <pre><code>// webpack.base.js
module.exports = {// ...module: {rules: [{test: /.(ts|tsx)$/,use: ['thread-loader', 'babel-loader']}]}
} 
</code></pre> 
  <p>由于<strong>thread-loader</strong>不支持抽离css插件<strong>MiniCssExtractPlugin.loader</strong>(下面会讲),所以这里只配置了多进程解析<strong>js</strong>,开启多线程也是需要启动时间,大约<strong>600ms</strong>左右,所以适合规模比较大的项目。</p> 
  <h3>6.4 配置alias别名</h3> 
  <p><strong>webpack</strong>支持设置别名<strong>alias</strong>,设置别名可以让后续引用的地方减少路径的复杂度。</p> 
  <p>修改<strong>webpack.base.js</strong></p> 
  <pre><code>module.export = {// ... resolve: {// ...alias: {'@': path.join(__dirname, '../src')}}
} 
</code></pre> 
  <p>修改<strong>tsconfig.json</strong>,添加<strong>baseUrl</strong>和<strong>paths</strong></p> 
  <pre><code>{"compilerOptions": {// ..."baseUrl": ".","paths": {"@/*": ["src/*"]}}
} 
</code></pre> 
  <p>配置修改完成后,在项目中使用 <strong>@/xxx.xx</strong>,就会指向项目中<strong>src/xxx.xx,<strong>在</strong>js/ts</strong>文件和<strong>css</strong>文件中都可以用。</p> 
  <p><strong>src/App.tsx</strong>可以修改为</p> 
  <pre><code>import React from 'react'
import smallImg from '@/assets/imgs/5kb.png'
import bigImg from '@/assets/imgs/22kb.png'
import '@/app.css'
import '@/app.less'

function App() {return (<><img src={smallImg} alt="小于10kb的图片" /><img src={bigImg} alt="大于于10kb的图片" /><div className='smallImg'></div> {/* 小图片背景容器 */}<div className='bigImg'></div> {/* 大图片背景容器 */}</>)
}
export default App 
</code></pre> 
  <p><strong>src/app.less</strong>可以修改为</p> 
  <pre><code>// app.less
#root {.smallImg {width: 69px;height: 75px;background: url('@/assets/imgs/5kb.png') no-repeat;}.bigImg {width: 232px;height: 154px;background: url('@/assets/imgs/22kb.png') no-repeat;}
} 
</code></pre> 
  <h3>6.5 缩小loader作用范围</h3> 
  <p>一般第三库都是已经处理好的,不需要再次使用<strong>loader</strong>去解析,可以按照实际情况合理配置<strong>loader</strong>的作用范围,来减少不必要的<strong>loader</strong>解析,节省时间,通过使用 <strong>include</strong>和<strong>exclude</strong> 两个配置项,可以实现这个功能,常见的例如:</p> 
  <ul> 
   <li><strong>include</strong>:只解析该选项配置的模块</li> 
   <li><strong>exclude</strong>:不解该选项配置的模块,优先级更高</li> 
  </ul> 
  <p>修改<strong>webpack.base.js</strong></p> 
  <pre><code>// webpack.base.js
const path = require('path')
module.exports = {// ...module: {rules: [{include: [path.resolve(__dirname, '../src')], 只对项目src文件的ts,tsx进行loader解析test: /.(ts|tsx)$/,use: ['thread-loader', 'babel-loader']}]}
} 
</code></pre> 
  <p>其他<strong>loader</strong>也是相同的配置方式,如果除<strong>src</strong>文件外也还有需要解析的,就把对应的目录地址加上就可以了,比如需要引入<strong>antd</strong>的<strong>css</strong>,可以把<strong>antd</strong>的文件目录路径添加解析<strong>css</strong>规则到<strong>include</strong>里面。</p> 
  <h3>6.6 精确使用loader</h3> 
  <p><strong>loader</strong>在<strong>webpack</strong>构建过程中使用的位置是在<strong>webpack</strong>构建模块依赖关系引入新文件时,会根据文件后缀来倒序遍历<strong>rules</strong>数组,如果文件后缀和<strong>test</strong>正则匹配到了,就会使用该<strong>rule</strong>中配置的<strong>loader</strong>依次对文件源代码进行处理,最终拿到处理后的<strong>sourceCode</strong>结果,可以通过避免使用无用的<strong>loader</strong>解析来提升构建速度,比如使用<strong>less-loader</strong>解析<strong>css</strong>文件。</p> 
  <p>可以拆分上面配置的<strong>less</strong>和<strong>css</strong>, 避免让<strong>less-loader</strong>再去解析<strong>css</strong>文件</p> 
  <pre><code>// webpack.base.js
// ...
module.exports = {module: {// ...rules: [// ...{test: /.css$/, //匹配所有的 css 文件include: [path.resolve(__dirname, '../src')],use: ['style-loader','css-loader','postcss-loader']},{test: /.less$/, //匹配所有的 less 文件include: [path.resolve(__dirname, '../src')],use: ['style-loader','css-loader','postcss-loader','less-loader']},]}
} 
</code></pre> 
  <p><strong>ts</strong>和<strong>tsx</strong>也是如此,<strong>ts</strong>里面是不能写<strong>jsx</strong>语法的,所以可以尽可能避免使用 <strong>@babel/preset-react</strong>对 <strong>.ts</strong> 文件语法做处理。</p> 
  <h3>6.7 缩小模块搜索范围</h3> 
  <p><strong>node</strong>里面模块有三种</p> 
  <ul> 
   <li><strong>node</strong>核心模块</li> 
   <li><strong>node_modules</strong>模块</li> 
   <li>自定义文件模块</li> 
  </ul> 
  <p>使用<strong>require</strong>和<strong>import</strong>引入模块时如果有准确的相对或者绝对路径,就会去按路径查询,如果引入的模块没有路径,会优先查询<strong>node</strong>核心模块,如果没有找到会去当前目录下<strong>node_modules</strong>中寻找,如果没有找到会查从父级文件夹查找<strong>node_modules</strong>,一直查到系统<strong>node</strong>全局模块。</p> 
  <p>这样会有两个问题,一个是当前项目没有安装某个依赖,但是上一级目录下<strong>node_modules</strong>或者全局模块有安装,就也会引入成功,但是部署到服务器时可能就会找不到造成报错,另一个问题就是一级一级查询比较消耗时间。可以告诉<strong>webpack</strong>搜索目录范围,来规避这两个问题。</p> 
  <p>修改<strong>webpack.base.js</strong></p> 
  <pre><code>// webpack.base.js
const path = require('path')
module.exports = {// ...resolve: { // ... modules: [path.resolve(__dirname, '../node_modules')], // 查找第三方模块只在本项目的node_modules中查找},
} 
</code></pre> 
  <h3>6.8 devtool 配置</h3> 
  <p>开发过程中或者打包后的代码都是<strong>webpack</strong>处理后的代码,如果进行调试肯定希望看到源代码,而不是编译后的代码, source map就是用来做源码映射的,不同的映射模式会明显影响到构建和重新构建的速度, <strong>devtool</strong>选项就是<strong>webpack</strong>提供的选择源码映射方式的配置。</p> 
  <p><strong>devtool</strong>的命名规则为 <strong>^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$</strong></p> 
  <table> 
   <thead> 
    <tr> 
     <th>关键字</th> 
     <th>描述</th> 
    </tr> 
   </thead> 
   <tbody> 
    <tr> 
     <td>inline</td> 
     <td>代码内通过 dataUrl 形式引入 SourceMap</td> 
    </tr> 
    <tr> 
     <td>hidden</td> 
     <td>生成 SourceMap 文件,但不使用</td> 
    </tr> 
    <tr> 
     <td>eval</td> 
     <td><code>eval(...)</code> 形式执行代码,通过 dataUrl 形式引入 SourceMap</td> 
    </tr> 
    <tr> 
     <td>nosources</td> 
     <td>不生成 SourceMap</td> 
    </tr> 
    <tr> 
     <td>cheap</td> 
     <td>只需要定位到行信息,不需要列信息</td> 
    </tr> 
    <tr> 
     <td>module</td> 
     <td>展示源代码中的错误位置</td> 
    </tr> 
   </tbody> 
  </table> 
  <p>开发环境推荐:<strong>eval-cheap-module-source-map</strong></p> 
  <ul> 
   <li>本地开发首次打包慢点没关系,因为 <strong>eval</strong> 缓存的原因, 热更新会很快</li> 
   <li>开发中,我们每行代码不会写的太长,只需要定位到行就行,所以加上 <strong>cheap</strong></li> 
   <li>我们希望能够找到源代码的错误,而不是打包后的,所以需要加上 <strong>module</strong></li> 
  </ul> 
  <p>修改<strong>webpack.dev.js</strong></p> 
  <pre><code>// webpack.dev.js
module.exports = {// ...devtool: 'eval-cheap-module-source-map'
} 
</code></pre> 
  <p>打包环境推荐:<strong>none</strong>(就是不配置<strong>devtool</strong>选项了,不是配置<strong>devtool</strong>: ‘<strong>none</strong>’)</p> 
  <pre><code>// webpack.prod.js
module.exports = {// ...// devtool: '', // 不用配置devtool此项
} 
</code></pre> 
  <ul> 
   <li><strong>none</strong>话调试只能看到编译后的代码,也不会泄露源代码,打包速度也会比较快。</li> 
   <li>只是不方便线上排查问题, 但一般都可以根据报错信息在本地环境很快找出问题所在。</li> 
  </ul> 
  <h3>6.9 其他优化配置</h3> 
  <p>除了上面的配置外,<strong>webpack</strong>还提供了其他的一些优化方式,本次搭建没有使用到,所以只简单罗列下</p> 
  <ul> 
   <li><strong>externals</strong>: 外包拓展,打包时会忽略配置的依赖,会从上下文中寻找对应变量</li> 
   <li><strong>module.noParse</strong>: 匹配到设置的模块,将不进行依赖解析,适合<strong>jquery</strong>,<strong>boostrap</strong>这类不依赖外部模块的包</li> 
   <li><strong>ignorePlugin</strong>: 可以使用正则忽略一部分文件,常在使用多语言的包时可以把非中文语言包过滤掉</li> 
  </ul> 
  <h2>七. 优化构建结果文件</h2> 
  <h3>7.1 webpack包分析工具</h3> 
  <p>webpack-bundle-analyzer是分析<strong>webpack</strong>打包后文件的插件,使用交互式可缩放树形图可视化 <strong>webpack</strong> 输出文件的大小。通过该插件可以对打包后的文件进行观察和分析,可以方便我们对不完美的地方针对性的优化,安装依赖:</p> 
  <pre><code>npm install webpack-bundle-analyzer -D 
</code></pre> 
  <p>修改<strong>webpack.analy.js</strong></p> 
  <pre><code>// webpack.analy.js
const prodConfig = require('./webpack.prod.js')
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();
const { merge } = require('webpack-merge')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') // 引入分析打包结果插件
module.exports = smp.wrap(merge(prodConfig, {plugins: [new BundleAnalyzerPlugin() // 配置分析打包结果插件]
})) 
</code></pre> 
  <p>配置好后,执行<strong>npm run build:analy</strong>命令,打包完成后浏览器会自动打开窗口,可以看到打包文件的分析结果页面,可以看到各个文件所占的资源大小。</p> <a href="http://img.e-com-net.com/image/info8/f779257b94cf4001b046e348f312d2ce.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/f779257b94cf4001b046e348f312d2ce.jpg" width="650" height="363" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第13张图片" style="border:1px solid black;"></a> 
  <h3>7.2 抽取css样式文件</h3> 
  <p>在开发环境我们希望<strong>css</strong>嵌入在<strong>style</strong>标签里面,方便样式热替换,但打包时我们希望把<strong>css</strong>单独抽离出来,方便配置缓存策略。而插件mini-css-extract-plugin就是来帮我们做这件事的,安装依赖:</p> 
  <pre><code>npm i mini-css-extract-plugin -D 
</code></pre> 
  <p>修改<strong>webpack.base.js</strong>, 根据环境变量设置开发环境使用<strong>style-looader</strong>,打包模式抽离<strong>css</strong></p> 
  <pre><code>// webpack.base.js
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const isDev = process.env.NODE_ENV === 'development' // 是否是开发模式
module.exports = {// ...module: { rules: [// ...{test: /.css$/, //匹配所有的 css 文件include: [path.resolve(__dirname, '../src')],use: [isDev ? 'style-loader' : MiniCssExtractPlugin.loader, // 开发环境使用style-looader,打包模式抽离css'css-loader','postcss-loader']},{test: /.less$/, //匹配所有的 less 文件include: [path.resolve(__dirname, '../src')],use: [isDev ? 'style-loader' : MiniCssExtractPlugin.loader, // 开发环境使用style-looader,打包模式抽离css'css-loader','postcss-loader','less-loader']},]},// ...
} 
</code></pre> 
  <p>再修改<strong>webpack.prod.js</strong>, 打包时添加抽离css插件</p> 
  <pre><code>// webpack.prod.js
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = merge(baseConfig, {mode: 'production',plugins: [// ...// 抽离css插件new MiniCssExtractPlugin({filename: 'static/css/[name].css' // 抽离css的输出目录和名称}),]
}) 
</code></pre> 
  <p>配置完成后,在开发模式<strong>css</strong>会嵌入到<strong>style</strong>标签里面,方便样式热替换,打包时会把<strong>css</strong>抽离成单独的<strong>css</strong>文件。</p> 
  <h3>7.3 压缩css文件</h3> 
  <p>上面配置了打包时把<strong>css</strong>抽离为单独<strong>css</strong>文件的配置,打开打包后的文件查看,可以看到默认<strong>css</strong>是没有压缩的,需要手动配置一下压缩<strong>css</strong>的插件。</p> <a href="http://img.e-com-net.com/image/info8/849cdc6dae164307b47cdc96ca26f92b.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/849cdc6dae164307b47cdc96ca26f92b.jpg" width="650" height="281" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第14张图片" style="border:1px solid black;"></a> 
  <p>可以借助css-minimizer-webpack-plugin来压缩css,安装依赖</p> 
  <pre><code>npm i css-minimizer-webpack-plugin -D 
</code></pre> 
  <p>修改<strong>webpack.prod.js</strong>文件, 需要在优化项optimization下的minimizer属性中配置</p> 
  <pre><code>// webpack.prod.js
// ...
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {// ...optimization: {minimizer: [new CssMinimizerPlugin(), // 压缩css],},
} 
</code></pre> 
  <p>再次执行打包就可以看到<strong>css</strong>已经被压缩了。</p> 
  <h3>7.4 压缩js文件</h3> 
  <p>设置<strong>mode</strong>为<strong>production</strong>时,<strong>webpack</strong>会使用内置插件terser-webpack-plugin压缩<strong>js</strong>文件,该插件默认支持多线程压缩,但是上面配置<strong>optimization.minimizer</strong>压缩<strong>css</strong>后,<strong>js</strong>压缩就失效了,需要手动再添加一下,<strong>webpack</strong>内部安装了该插件,由于<strong>pnpm</strong>解决了幽灵依赖问题,如果用的<strong>pnpm</strong>的话,需要手动再安装一下依赖。</p> 
  <pre><code>npm i terser-webpack-plugin -D 
</code></pre> 
  <p>修改<strong>webpack.prod.js</strong>文件</p> 
  <pre><code>// ...
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {// ...optimization: {minimizer: [// ...new TerserPlugin({ // 压缩jsparallel: true, // 开启多线程压缩terserOptions: {compress: {pure_funcs: ["console.log"] // 删除console.log}}}),],},
} 
</code></pre> 
  <p>配置完成后再打包,<strong>css</strong>和<strong>js</strong>就都可以被压缩了。</p> 
  <h3>7.5 合理配置打包文件hash</h3> 
  <p>项目维护的时候,一般只会修改一部分代码,可以合理配置文件缓存,来提升前端加载页面速度和减少服务器压力,而<strong>hash</strong>就是浏览器缓存策略很重要的一部分。<strong>webpack</strong>打包的<strong>hash</strong>分三种:</p> 
  <ul> 
   <li><strong>hash</strong>:跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的<strong>hash</strong>值都会更改,并且全部文件都共用相同的<strong>hash</strong>值</li> 
   <li><strong>chunkhash</strong>:不同的入口文件进行依赖文件解析、构建对应的<strong>chunk</strong>,生成对应的哈希值,文件本身修改或者依赖文件修改,<strong>chunkhash</strong>值会变化</li> 
   <li><strong>contenthash</strong>:每个文件自己单独的 <strong>hash</strong> 值,文件的改动只会影响自身的 <strong>hash</strong> 值</li> 
  </ul> 
  <p><strong>hash</strong>是在输出文件时配置的,格式是<strong>filename: “[name].[chunkhash:8][ext]”</strong>,<strong>[xx]</strong> 格式是<strong>webpack</strong>提供的占位符, <strong>:8</strong>是生成<strong>hash</strong>的长度。</p> 
  <table> 
   <thead> 
    <tr> 
     <th>占位符</th> 
     <th>解释</th> 
    </tr> 
   </thead> 
   <tbody> 
    <tr> 
     <td>ext</td> 
     <td>文件后缀名</td> 
    </tr> 
    <tr> 
     <td>name</td> 
     <td>文件名</td> 
    </tr> 
    <tr> 
     <td>path</td> 
     <td>文件相对路径</td> 
    </tr> 
    <tr> 
     <td>folder</td> 
     <td>文件所在文件夹</td> 
    </tr> 
    <tr> 
     <td>hash</td> 
     <td>每次构建生成的唯一 hash 值</td> 
    </tr> 
    <tr> 
     <td>chunkhash</td> 
     <td>根据 chunk 生成 hash 值</td> 
    </tr> 
    <tr> 
     <td>contenthash</td> 
     <td>根据文件内容生成hash 值</td> 
    </tr> 
   </tbody> 
  </table> 
  <p>因为<strong>js</strong>我们在生产环境里会把一些公共库和程序入口文件区分开,单独打包构建,采用<strong>chunkhash</strong>的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响,可以继续使用浏览器缓存,所以<strong>js</strong>适合使用<strong>chunkhash</strong>。</p> 
  <p><strong>css</strong>和图片资源媒体资源一般都是单独存在的,可以采用<strong>contenthash</strong>,只有文件本身变化后会生成新<strong>hash</strong>值。</p> 
  <p>修改<strong>webpack.base.js</strong>,把<strong>js</strong>输出的文件名称格式加上<strong>chunkhash</strong>,把<strong>css</strong>和图片媒体资源输出格式加上<strong>contenthash</strong></p> 
  <pre><code>// webpack.base.js
// ...
module.exports = {// 打包文件出口output: {filename: 'static/js/[name].[chunkhash:8].js', // // 加上[chunkhash:8]// ...},module: {rules: [{test:/.(png|jpg|jpeg|gif|svg)$/, // 匹配图片文件// ...generator:{ filename:'static/images/[name].[contenthash:8][ext]' // 加上[contenthash:8]},},{test:/.(woff2?|eot|ttf|otf)$/, // 匹配字体文件// ...generator:{ filename:'static/fonts/[name].[contenthash:8][ext]', // 加上[contenthash:8]},},{test:/.(mp4|webm|ogg|mp3|wav|flac|aac)$/, // 匹配媒体文件// ...generator:{ filename:'static/media/[name].[contenthash:8][ext]', // 加上[contenthash:8]},},]},// ...
} 
</code></pre> 
  <p>再修改<strong>webpack.prod.js</strong>,修改抽离<strong>css</strong>文件名称格式</p> 
  <pre><code>// webpack.prod.js
// ...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = merge(baseConfig, {mode: 'production',plugins: [// 抽离css插件new MiniCssExtractPlugin({filename: 'static/css/[name].[contenthash:8].css' // 加上[contenthash:8]}),// ...],// ...
}) 
</code></pre> 
  <p>再次打包就可以看到文件后面的<strong>hash</strong>了</p> 
  <h3>7.6 代码分割第三方包和公共模块</h3> 
  <p>一般第三方包的代码变化频率比较小,可以单独把<strong>node_modules</strong>中的代码单独打包, 当第三包代码没变化时,对应<strong>chunkhash</strong>值也不会变化,可以有效利用浏览器缓存,还有公共的模块也可以提取出来,避免重复打包加大代码整体体积, <strong>webpack</strong>提供了代码分隔功能, 需要我们手动在优化项optimization中手动配置下代码分隔splitChunks规则。</p> 
  <p>修改<strong>webpack.prod.js</strong></p> 
  <pre><code>module.exports = {// ...optimization: {// ...splitChunks: { // 分隔代码cacheGroups: {vendors: { // 提取node_modules代码test: /node_modules/, // 只匹配node_modules里面的模块name: 'vendors', // 提取文件命名为vendors,js后缀和chunkhash会自动加minChunks: 1, // 只要使用一次就提取出来chunks: 'initial', // 只提取初始化就能获取到的模块,不管异步的minSize: 0, // 提取代码体积大于0就提取出来priority: 1, // 提取优先级为1},commons: { // 提取页面公共代码name: 'commons', // 提取文件命名为commonsminChunks: 2, // 只要使用两次就提取出来chunks: 'initial', // 只提取初始化就能获取到的模块,不管异步的minSize: 0, // 提取代码体积大于0就提取出来}}}}
} 
</code></pre> 
  <p>配置完成后执行打包,可以看到<strong>node_modules</strong>里面的模块被抽离到<strong>verdors.ec725ef1.js</strong>中,业务代码在<strong>main.9a6bf38a.js</strong>中。</p> <a href="http://img.e-com-net.com/image/info8/7d0c18dc63254676827b529e46800a94.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/7d0c18dc63254676827b529e46800a94.jpg" width="650" height="245" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第15张图片" style="border:1px solid black;"></a> 
  <p>测试一下,此时<strong>verdors.js</strong>的<strong>chunkhash</strong>是<strong>ec725ef1</strong>,<strong>main.js</strong>文件的<strong>chunkhash</strong>是<strong>9a6bf38a</strong>,改动一下<strong>App.tsx</strong>,再次打包,可以看到下图<strong>main.js</strong>的<strong>chunkhash</strong>值变化了,但是<strong>vendors.js</strong>的<strong>chunkhash</strong>还是原先的,这样发版后,浏览器就可以继续使用缓存中的<strong>verdors.ec725ef1.js</strong>,只需要重新请求<strong>main.js</strong>就可以了。</p> <a href="http://img.e-com-net.com/image/info8/c02f3fc830594fd4afa7b04db7331c33.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/c02f3fc830594fd4afa7b04db7331c33.jpg" width="650" height="225" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第16张图片" style="border:1px solid black;"></a> 
  <h3>7.7 tree-shaking清理未引用js</h3> 
  <p>Tree Shaking的意思就是摇树,伴随着摇树这个动作,树上的枯叶都会被摇晃下来,这里的<strong>tree-shaking</strong>在代码中摇掉的是未使用到的代码,也就是未引用的代码,最早是在<strong>rollup</strong>库中出现的,<strong>webpack</strong>在<strong>2</strong>版本之后也开始支持。模式<strong>mode</strong>为<strong>production</strong>时就会默认开启<strong>tree-shaking</strong>功能以此来标记未引入代码然后移除掉,测试一下。</p> 
  <p>在<strong>src/components</strong>目录下新增<strong>Demo1</strong>,<strong>Demo2</strong>两个组件</p> 
  <pre><code>// src/components/Demo1.tsx
import React from "react";
function Demo1() {return <h3>我是Demo1组件</h3>
}
export default Demo1

// src/components/Demo2.tsx
import React from "react";
function Demo2() {return <h3>我是Demo2组件</h3>
}
export default Demo2 
</code></pre> 
  <p>再在<strong>src/components</strong>目录下新增<strong>index.ts</strong>, 把<strong>Demo1</strong>和<strong>Demo2</strong>组件引入进来再暴露出去</p> 
  <pre><code>// src/components/index.ts
export { default as Demo1 } from './Demo1'
export { default as Demo2 } from './Demo2' 
</code></pre> 
  <p>在<strong>App.tsx</strong>中引入两个组件,但只使用<strong>Demo1</strong>组件</p> 
  <pre><code>// ...
import { Demo1, Demo2 } from '@/components'

function App() {return <Demo1 />
}
export default App 
</code></pre> 
  <p>执行打包,可以看到在<strong>main.js</strong>中搜索<strong>Demo</strong>,只搜索到了<strong>Demo1</strong>, 代表<strong>Demo2</strong>组件被<strong>tree-shaking</strong>移除掉了。</p> <a href="http://img.e-com-net.com/image/info8/76a46f8806684034adc552b723104546.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/76a46f8806684034adc552b723104546.jpg" width="650" height="236" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第17张图片" style="border:1px solid black;"></a> 
  <h3>7…8 tree-shaking清理未使用css</h3> 
  <p><strong>js</strong>中会有未使用到的代码,<strong>css</strong>中也会有未被页面使用到的样式,可以通过purgecss-webpack-plugin插件打包的时候移除未使用到的<strong>css</strong>样式,这个插件是和mini-css-extract-plugin插件配合使用的,在上面已经安装过,还需要<strong>glob-all</strong>来选择要检测哪些文件里面的类名和<strong>id</strong>还有标签名称, 安装依赖:</p> 
  <pre><code>npm i purgecss-webpack-plugin glob-all -D 
</code></pre> 
  <p>修改<strong>webpack.prod.js</strong></p> 
  <pre><code>// webpack.prod.js
// ...
const globAll = require('glob-all')
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {// ...plugins: [// 抽离css插件new MiniCssExtractPlugin({filename: 'static/css/[name].[contenthash:8].css'}),// 清理无用cssnew PurgeCSSPlugin({// 检测src下所有tsx文件和public下index.html中使用的类名和id和标签名称// 只打包这些文件中用到的样式paths: globAll.sync([`${path.join(__dirname, '../src')}/**/*.tsx`,path.join(__dirname, '../public/index.html')]),}),]
} 
</code></pre> 
  <p>测试一下,用上面配置解析图片文件代码拿过来,修改<strong>App.tsx</strong></p> 
  <pre><code>import React from 'react'
import './app.css'
import './app.less'

function App() {return (<><div className='smallImg'></div><div className='bigImg'></div></>)
}
export default App 
</code></pre> 
  <p><strong>App.tsx</strong>中有两个<strong>div</strong>,类名分别是<strong>smallImg</strong>和<strong>bigImg</strong>,当前<strong>app.less</strong>代码为</p> 
  <pre><code>#root {.smallImg {width: 69px;height: 75px;background: url('./assets/imgs/5kb.png') no-repeat;}.bigImg {width: 232px;height: 154px;background: url('./assets/imgs/22kb.png') no-repeat;}
} 
</code></pre> 
  <p>此时先执行一下打包,查看<strong>main.css</strong></p> <a href="http://img.e-com-net.com/image/info8/a0414bfea3804a6c9e6fe06c5e52d0a8.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/a0414bfea3804a6c9e6fe06c5e52d0a8.jpg" width="650" height="288" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第18张图片" style="border:1px solid black;"></a> 
  <p>因为页面中中有<strong>h2</strong>标签, <strong>smallImg</strong>和<strong>bigImg</strong>类名,所以打包后的<strong>css</strong>也有,此时修改一下<strong>app.less</strong>中的 <strong>.smallImg</strong>为 <strong>.smallImg1</strong>,此时 <strong>.smallImg1</strong>就是无用样式了,因为没有页面没有类名为 <strong>.smallImg1</strong>的节点,再打包后查看 <strong>main.css</strong></p> <a href="http://img.e-com-net.com/image/info8/a2319834d10347d99de721749aa239e4.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/a2319834d10347d99de721749aa239e4.jpg" width="650" height="244" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第19张图片" style="border:1px solid black;"></a> 
  <p>可以看到<strong>main.css</strong>已经没有 <strong>.smallImg1</strong>类名的样式了,做到了删除无用<strong>css</strong>的功能。</p> 
  <p>但是<strong>purgecss-webpack-plugin</strong>插件不是全能的,由于项目业务代码的复杂,插件不能百分百识别哪些样式用到了,哪些没用到,所以请不要寄希望于它能够百分百完美解决你的问题,这个是不现实的。</p> 
  <p>插件本身也提供了一些白名单<strong>safelist</strong>属性,符合配置规则选择器都不会被删除掉,比如使用了组件库antd, <strong>purgecss-webpack-plugin</strong>插件检测<strong>src</strong>文件下<strong>tsx</strong>文件中使用的类名和<strong>id</strong>时,是检测不到在<strong>src</strong>中使用<strong>antd</strong>组件的类名的,打包的时候就会把<strong>antd</strong>的类名都给过滤掉,可以配置一下安全选择列表,避免删除<strong>antd</strong>组件库的前缀<strong>ant</strong>。</p> 
  <pre><code>new PurgeCSSPlugin({// ...safelist: {standard: [/^ant-/], // 过滤以ant-开头的类名,哪怕没用到也不删除}
}) 
</code></pre> 
  <h3>7.9 资源懒加载</h3> 
  <p>像<strong>react</strong>,<strong>vue</strong>等单页应用打包默认会打包到一个<strong>js</strong>文件中,虽然使用代码分割可以把<strong>node_modules</strong>模块和<strong>公共模块</strong>分离,但页面初始加载还是会把整个项目的代码下载下来,其实只需要公共资源和当前页面的资源就可以了,其他页面资源可以等使用到的时候再加载,可以有效提升首屏加载速度。</p> 
  <p><strong>webpack</strong>默认支持资源懒加载,只需要引入资源使用<strong>import</strong>语法来引入资源,<strong>webpack</strong>打包的时候就会自动打包为单独的资源文件,等使用到的时候动态加载。</p> 
  <p>以懒加载组件和<strong>css</strong>为例,新建懒加载组件<strong>src/components/LazyDemo.tsx</strong></p> 
  <pre><code>import React from "react";

function LazyDemo() {return <h3>我是懒加载组件组件</h3>
}

export default LazyDemo 
</code></pre> 
  <p>修改<strong>App.tsx</strong></p> 
  <pre><code>import React, { lazy, Suspense, useState } from 'react'
const LazyDemo = lazy(() => import('@/components/LazyDemo')) // 使用import语法配合react的Lazy动态引入资源

function App() {const [ show, setShow ] = useState(false)// 点击事件中动态引入css, 设置show为trueconst onClick = () => {import('./app.css')setShow(true)}return (<><h2 onClick={onClick}>展示</h2>{/* show为true时加载LazyDemo组件 */}{ show && <Suspense fallback={null}><LazyDemo /></Suspense> }</>)
}
export default App 
</code></pre> 
  <p>点击展示文字时,才会动态加载<strong>app.css</strong>和<strong>LazyDemo</strong>组件的资源。</p> <a href="http://img.e-com-net.com/image/info8/3d01b0ec3fb24e6c8079160338f6da7c.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/3d01b0ec3fb24e6c8079160338f6da7c.jpg" width="611" height="332" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第20张图片" style="border:1px solid black;"></a> 
  <h3>7.10 资源预加载</h3> 
  <p>上面配置了资源懒加载后,虽然提升了首屏渲染速度,但是加载到资源的时候会有一个去请求资源的延时,如果资源比较大会出现延迟卡顿现象,可以借助<strong>link</strong>标签的<strong>rel</strong>属性<strong>prefetch</strong>与<strong>preload</strong>,<strong>link</strong>标签除了加载<strong>css</strong>之外也可以加载<strong>js</strong>资源,设置<strong>rel</strong>属性可以规定<strong>link</strong>提前加载资源,但是加载资源后不执行,等用到了再执行。</p> 
  <p><strong>rel的属性值</strong></p> 
  <ul> 
   <li><strong>preload</strong>是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源。</li> 
   <li><strong>prefetch</strong>是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源,会在空闲时加载。</li> 
  </ul> 
  <p>对于当前页面很有必要的资源使用 <strong>preload</strong> ,对于可能在将来的页面中使用的资源使用 <strong>prefetch</strong>。</p> 
  <p><strong>webpack v4.6.0+</strong> 增加了对预获取和预加载的支持,使用方式也比较简单,在<strong>import</strong>引入动态资源时使用<strong>webpack</strong>的魔法注释</p> 
  <pre><code>// 单个目标
import(/* webpackChunkName: "my-chunk-name" */ // 资源打包后的文件chunkname/* webpackPrefetch: true */ // 开启prefetch预获取/* webpackPreload: true */ // 开启preload预获取'./module'
); 
</code></pre> 
  <p>测试一下,在<strong>src/components</strong>目录下新建<strong>PreloadDemo.tsx</strong>, <strong>PreFetchDemo.tsx</strong></p> 
  <pre><code>// 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 
</code></pre> 
  <p>修改<strong>App.tsx</strong></p> 
  <pre><code>import React, { lazy, Suspense, useState } from 'react'

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

function App() {const [ show, setShow ] = useState(false)const onClick = () => {setShow(true)}return (<><h2 onClick={onClick}>展示</h2>{/* show为true时加载组件 */}{ show && (<><Suspense fallback={null}><PreloadDemo /></Suspense><Suspense fallback={null}><PreFetchDemo /></Suspense></>) }</>)
}
export default App 
</code></pre> 
  <p>然后打包后查看效果,页面初始化时预加载了<strong>PreFetchDemo.js</strong>组件资源,但是不执行里面的代码,等点击展示按钮后从预加载的资源中直接取出来执行,不用再从服务器请求,节省了很多时间。</p> <a href="http://img.e-com-net.com/image/info8/4efe0d66549843e5b7b16c293e89efb8.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/4efe0d66549843e5b7b16c293e89efb8.jpg" width="650" height="311" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第21张图片" style="border:1px solid black;"></a> 
  <blockquote> 
   <p>在测试时发现只有<strong>js</strong>资源设置<strong>prefetch</strong>模式才能触发资源预加载,<strong>preload</strong>模式触发不了,<strong>css</strong>和图片等资源不管设置<strong>prefetch</strong>还是<strong>preload</strong>都不能触发,不知道是哪里没配置好。</p> 
  </blockquote> 
  <h3>7.11 打包时生成gzip文件</h3> 
  <p>前端代码在浏览器运行,需要从服务器把<strong>html</strong>,<strong>css</strong>,<strong>js</strong>资源下载执行,下载的资源体积越小,页面加载速度就会越快。一般会采用<strong>gzip</strong>压缩,现在大部分浏览器和服务器都支持<strong>gzip</strong>,可以有效减少静态资源文件大小,压缩率在 <strong>70%</strong> 左右。</p> 
  <p><strong>nginx</strong>可以配置<strong>gzip: on</strong>来开启压缩,但是只在<strong>nginx</strong>层面开启,会在每次请求资源时都对资源进行压缩,压缩文件会需要时间和占用服务器<strong>cpu</strong>资源,更好的方式是前端在打包的时候直接生成<strong>gzip</strong>资源,服务器接收到请求,可以直接把对应压缩好的<strong>gzip</strong>文件返回给浏览器,节省时间和<strong>cpu</strong>。</p> 
  <p><strong>webpack</strong>可以借助compression-webpack-plugin 插件在打包时生成 <strong>gzip</strong> 文章,安装依赖</p> 
  <pre><code>npm i compression-webpack-plugin -D 
</code></pre> 
  <p>添加配置,修改<strong>webpack.prod.js</strong></p> 
  <pre><code>const glob = require('glob')
const CompressionPlugin= require('compression-webpack-plugin')
module.exports = {// ...plugins: [ // ... new CompressionPlugin({test: /.(js|css)$/, // 只生成css,js压缩文件filename: '[path][base].gz', // 文件命名algorithm: 'gzip', // 压缩格式,默认是gziptest: /.(js|css)$/, // 只生成css,js压缩文件threshold: 10240, // 只有大小大于该值的资源会被处理。默认值是 10kminRatio: 0.8 // 压缩率,默认值是 0.8})]
} 
</code></pre> 
  <p>配置完成后再打包,可以看到打包后js的目录下多了一个 <strong>.gz</strong> 结尾的文件</p> <a href="http://img.e-com-net.com/image/info8/cfa57ff6be7f4500baedaf91af181615.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/cfa57ff6be7f4500baedaf91af181615.jpg" width="650" height="266" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第22张图片" style="border:1px solid black;"></a> 
  <p>因为只有<strong>verdors.js</strong>的大小超过了<strong>10k</strong>, 所以只有它生成了<strong>gzip</strong>压缩文件,借助<strong>serve -s dist</strong>启动<strong>dist</strong>,查看<strong>verdors.js</strong>加载情况</p> <a href="http://img.e-com-net.com/image/info8/f38704cd97474090a6a475986d282365.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/f38704cd97474090a6a475986d282365.jpg" width="650" height="113" alt="【前端工程化】webpack5从零搭建完整的react18+ts开发和打包环境_第23张图片" style="border:1px solid black;"></a> 
  <p>可以看到<strong>verdors.js</strong>的原始大小是<strong>182kb</strong>, 使用<strong>gzip</strong>压缩后的文件只剩下了<strong>60.4kb</strong>,减少了<strong>70%</strong> 的大小,可以极大提升页面加载速度。</p> 
  <h2>八. 总结</h2> 
  <p>到目前为止已经使用<strong>webpack5</strong>把<strong>react18+ts</strong>的开发环境配置完成,并且配置比较完善的保留组件状态的<strong>热更新</strong>,以及常见的<strong>优化构建速度</strong>和<strong>构建结果</strong>的配置,完整代码已上传到webpack5-react-ts 。还有细节需要优化,比如把容易改变的配置单独写个<strong>config.js</strong>来配置,输出文件路径封装。这篇文章只是配置,如果想学好<strong>webpack</strong>,还需要学习<strong>webpack</strong>的构建原理以及<strong>loader</strong>和<strong>plugin</strong>的实现机制。</p> 
  <p>本文是总结自己在工作中使用<strong>webpack5</strong>搭建<strong>react+ts</strong>构建环境中使用到的配置, 肯定也很多没有做好的地方,后续有好的使用技巧和配置也会继续更新记录。</p> 
  <p>附上上面安装依赖的版本</p> 
  <pre><code>"devDependencies": {"@babel/core": "^7.18.2","@babel/plugin-proposal-decorators": "^7.18.2","@babel/plugin-transform-runtime": "^7.18.5","@babel/preset-env": "^7.18.2","@babel/preset-react": "^7.17.12","@babel/preset-typescript": "^7.17.12","@pmmmwh/react-refresh-webpack-plugin": "^0.5.7","@types/react": "^18.0.12","@types/react-dom": "^18.0.5","autoprefixer": "^10.4.7","babel-loader": "^8.2.5","compression-webpack-plugin": "^10.0.0","copy-webpack-plugin": "^11.0.0","core-js": "^3.23.0","cross-env": "^7.0.3","css-loader": "^6.7.1","css-minimizer-webpack-plugin": "^4.0.0","html-webpack-plugin": "^5.5.0","less": "^4.1.3","less-loader": "^11.0.0","mini-css-extract-plugin": "^2.6.1","postcss": "^8.4.14","postcss-loader": "^7.0.0","purgecss-webpack-plugin": "^4.1.3","react-refresh": "^0.14.0","speed-measure-webpack-plugin": "^1.5.0","style-loader": "^3.3.1","thread-loader": "^3.0.4","typescript": "^4.7.3","webpack": "^5.73.0","webpack-bundle-analyzer": "^4.5.0","webpack-cli": "^4.9.2","webpack-dev-server": "^4.9.1","webpack-merge": "^5.8.0"},"dependencies": {"react": "^18.1.0","react-dom": "^18.1.0"} 
</code></pre> 
 </div> 
</div>
                            </div>
                        </div>
                    </div>
                    <!--PC和WAP自适应版-->
                    <div id="SOHUCS" sid="1589252040465424384"></div>
                    <script type="text/javascript" src="/views/front/js/chanyan.js"></script>
                    <!-- 文章页-底部 动态广告位 -->
                    <div class="youdao-fixed-ad" id="detail_ad_bottom"></div>
                </div>
                <div class="col-md-3">
                    <div class="row" id="ad">
                        <!-- 文章页-右侧1 动态广告位 -->
                        <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_1"> </div>
                        </div>
                        <!-- 文章页-右侧2 动态广告位 -->
                        <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_2"></div>
                        </div>
                        <!-- 文章页-右侧3 动态广告位 -->
                        <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_3"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="container">
        <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(前端,webpack,javascript)</h4>
        <div id="paradigm-article-related">
            <div class="recommend-post mb30">
                <ul class="widget-links">
                    <li><a href="/article/1899498611876622336.htm"
                           title="基于PHP进程控制与Redis队列的异步任务实践——解决Excel导入接口超时问题" target="_blank">基于PHP进程控制与Redis队列的异步任务实践——解决Excel导入接口超时问题</a>
                        <span class="text-muted"></span>

                        <div>问题背景与解决方案问题场景在实现Excel数据导入功能时,遇到一个典型的生产者-消费者场景:主流程:Excel文件解析→数据校验→数据库事务写入附加流程:将成功数据推送给第三方系统当第三方接口响应缓慢时(实测平均耗时8-12秒),导致整体接口响应时间超出前端等待阈值,造成以下问题:前端显示系统错误(HTTP500)实际业务数据已完整入库用户体验与数据一致性存在割裂解决方案演进同步方案:直接顺序执行</div>
                    </li>
                    <li><a href="/article/1899486451452669952.htm"
                           title="tauri如何实现窗口拖动,自定义标题栏" target="_blank">tauri如何实现窗口拖动,自定义标题栏</a>
                        <span class="text-muted">爱音乐的程序猿</span>
<a class="tag" taget="_blank" href="/search/rust%E8%AF%AD%E8%A8%80/1.htm">rust语言</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/tauri/1.htm">tauri</a><a class="tag" taget="_blank" href="/search/rust/1.htm">rust</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E6%A1%8C%E9%9D%A2%E8%BD%AF%E4%BB%B6/1.htm">桌面软件</a><a class="tag" taget="_blank" href="/search/exe/1.htm">exe</a>
                        <div>文章目录一、tauri是什么?二、封装好的标题栏,引用修改即可使用三相关配置实现细节实现窗口拖动一、tauri是什么?Tauri是一个开源框架,用于创建跨平台的桌面应用程序。它使用Rust编程语言,并结合了现有的Web技术,如HTML、CSS和JavaScript。Tauri旨在提供一个快速、可靠和安全的方式来构建本地应用程序,同时保持Web开发的灵活性和易用性。它支持多个操作系统和架构,包括Wi</div>
                    </li>
                    <li><a href="/article/1899482286072590336.htm"
                           title="解释 TypeScript 中的类型系统,如何定义和使用类型?" target="_blank">解释 TypeScript 中的类型系统,如何定义和使用类型?</a>
                        <span class="text-muted">程序员黄同学</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/1.htm">前端开发</a><a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/typescript/1.htm">typescript</a><a class="tag" taget="_blank" href="/search/ubuntu/1.htm">ubuntu</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a>
                        <div>1.类型系统的核心作用TypeScript类型系统本质上是JavaScript的静态类型增强方案,提供三个核心价值:开发阶段类型检查(类似编译时eslint)更清晰的API文档(类型即文档)更好的IDE自动补全支持代码示例://错误示范:未指定类型导致潜在隐患functionadd(a,b){returna+b;}add('hello',123);//运行时错误但编译期不报错//正确类型标注fun</div>
                    </li>
                    <li><a href="/article/1899467911991455744.htm"
                           title="正则表达式(1)" target="_blank">正则表达式(1)</a>
                        <span class="text-muted">林深的林</span>
<a class="tag" taget="_blank" href="/search/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/1.htm">正则表达式</a>
                        <div>正则表达式概述正则表达式,又称正规表示法、常规表示法(英语:RegularExpression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。正则表达式类似于JSON,是一种通用的标准,被各种开发语言所支持,包括但不限于:Java,JavaScript,C,C++,C#,Python,SQL等等;因为在J</div>
                    </li>
                    <li><a href="/article/1899456187938697216.htm"
                           title="基于vue3实现的聊天机器人前端(附代码)" target="_blank">基于vue3实现的聊天机器人前端(附代码)</a>
                        <span class="text-muted">P7进阶路</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a>
                        <div>跟它说说话吧!一个活泼的伙伴,为你提供情感支持!??发送消息!import{ref,onMounted}from'vue';import{v4asuuidv4}from'uuid';//引入UUID生成库//响应式数据constmessage=ref('');//用户输入的消息constchatbox=ref(null);//聊天记录显示区的引用constchatId=ref(uuidv4());</div>
                    </li>
                    <li><a href="/article/1899443585930031104.htm"
                           title="Vue-前端发展史" target="_blank">Vue-前端发展史</a>
                        <span class="text-muted">lengzher_5601</span>
<a class="tag" taget="_blank" href="/search/Vue/1.htm">Vue</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a><a class="tag" taget="_blank" href="/search/js/1.htm">js</a><a class="tag" taget="_blank" href="/search/jsp/1.htm">jsp</a>
                        <div>文章目录Vue-前端发展史二、前端发展史1、UI框架2、JavaScript构建工具3、三端同一4、后端技术5、主流前端框架混合开发微信小程序Vue-前端发展史二、前端发展史1、UI框架Ant-Design:阿里巴巴出品,基于React的UI框架ElementUI、iview、ice:饿了么出品,基于Vue的UI框架BootStrap:Teitter推出的一个用于前端开发的开源工具包AmazeUI</div>
                    </li>
                    <li><a href="/article/1899442702450225152.htm"
                           title="HTML5 Canvas制作雪花飘落动画" target="_blank">HTML5 Canvas制作雪花飘落动画</a>
                        <span class="text-muted">坚持坚持那些年</span>

                        <div>本文还有配套的精品资源,点击获取简介:HTML5引入了Canvas元素,它赋予网页设计师丰富的绘图能力,允许通过JavaScript实现复杂的动画效果。本文将介绍如何结合HTML5的Canvas元素和JavaScript创建一个全屏的雪花飘落背景动画。通过定义雪花对象、创建雪花数组、编写主循环并利用requestAnimationFrame来绘制和更新雪花位置,我们能够实现一个逼真的雪花飘落动画效</div>
                    </li>
                    <li><a href="/article/1899442703771430912.htm"
                           title="用 Claude3.5 从零写扫雷游戏-实现蜂窝地图" target="_blank">用 Claude3.5 从零写扫雷游戏-实现蜂窝地图</a>
                        <span class="text-muted">selfboot0</span>
<a class="tag" taget="_blank" href="/search/AI%E7%BC%96%E7%A8%8B/1.htm">AI编程</a><a class="tag" taget="_blank" href="/search/ai/1.htm">ai</a><a class="tag" taget="_blank" href="/search/chatgpt/1.htm">chatgpt</a>
                        <div>上一篇用Claude3.5从零写扫雷游戏-基本功能篇中,在Claude3.5的帮助下,我这前端小白也基本完成了一个完整的扫雷游戏。不过只是传统的方格扫雷,如果换成蜂窝扫雷游戏,Claude3.5能实现吗?先来看成果吧,可以在在线扫雷游戏中体验:Claude3.5蜂窝扫雷实现考虑到前面已经实现了基本的方格扫雷,并且我们很机智的把逻辑,渲染,UI组件都分开了。那么实现蜂窝状的扫雷,也可以按照这个思路来</div>
                    </li>
                    <li><a href="/article/1899438534327070720.htm"
                           title="Web前端发展史" target="_blank">Web前端发展史</a>
                        <span class="text-muted">王珍岩</span>
<a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a>
                        <div>1、静态页面阶段那是1990年的12月25日,恰是西方的圣诞节,TimBerners-Lee在他的NeXT电脑上部署了第一套“主机-网站-浏览器”构成的Web系统,这标志BS架构的网站应用软件的开端,也是前端工程的开端。1993年4月Mosaic浏览器作为第一款正式的浏览器发布。1994年11月,鼎鼎大名的Navigator浏览器发布发布了,到年底W3C在Berners-Lee的主持下成立,标志着</div>
                    </li>
                    <li><a href="/article/1899437526028972032.htm"
                           title="探索创新:CanvasParticles - 点燃你的网页动态效果" target="_blank">探索创新:CanvasParticles - 点燃你的网页动态效果</a>
                        <span class="text-muted">柏赢安Simona</span>

                        <div>探索创新:CanvasParticles-点燃你的网页动态效果去发现同类优质开源项目:https://gitcode.com/是一个开源的JavaScript库,专注于在HTML5Canvas上创建引人入胜的粒子动画效果。如果你是Web开发者,正在寻找一种方法为你的网站增添独特的视觉吸引力,那么这个项目绝对值得你深入了解。项目简介CanvasParticles提供了一套简洁而强大的API,让你能够</div>
                    </li>
                    <li><a href="/article/1899437021672304640.htm"
                           title="探索CoreHTML5Canvas:创作动态Web图形的新工具" target="_blank">探索CoreHTML5Canvas:创作动态Web图形的新工具</a>
                        <span class="text-muted">郁英忆</span>

                        <div>探索CoreHTML5Canvas:创作动态Web图形的新工具去发现同类优质开源项目:https://gitcode.com/是一个强大的JavaScript库,专为开发者设计,旨在简化和增强在Web上创建交互式和动画图形的能力。这个项目利用HTML5Canvas元素,提供了一个简洁且高效的API,让开发人员可以轻松地构建出丰富的2D渲染效果。技术分析HTML5Canvas是HTML5的一个重要特</div>
                    </li>
                    <li><a href="/article/1899436895264370688.htm"
                           title="JavaEE 项目常见错误解决方案" target="_blank">JavaEE 项目常见错误解决方案</a>
                        <span class="text-muted">一弦一柱</span>
<a class="tag" taget="_blank" href="/search/JavaEE/1.htm">JavaEE</a><a class="tag" taget="_blank" href="/search/%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/1.htm">常见错误</a><a class="tag" taget="_blank" href="/search/%E4%B8%AD%E6%96%87%E4%B9%B1%E7%A0%81/1.htm">中文乱码</a><a class="tag" taget="_blank" href="/search/JSP/1.htm">JSP</a><a class="tag" taget="_blank" href="/search/404/1.htm">404</a>
                        <div>JavaEE项目常见错误解决方案数据库连接JavaBean获取不到数据库字段值或出现意料之外的值业务中出现null或""404NOTFOUNDGET请求中文乱码form表单提交中文乱码最近的实训中,练了一个比较基础的项目,JSP+Servlet+JavaBean,完成两张表的CRUD操作,前端使用Bootstrap和JQuery,交互使用AJAX,IDE选用Eclipse,在时间比较仓促的情况下完</div>
                    </li>
                    <li><a href="/article/1899435379816198144.htm"
                           title="前端框架的发展史" target="_blank">前端框架的发展史</a>
                        <span class="text-muted">Qpeterqiufengyi</span>
<a class="tag" taget="_blank" href="/search/%E4%B8%93%E6%9C%89%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A/1.htm">专有名词解释</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/1.htm">前端框架</a>
                        <div>1、htmlcss+div从1990年代初HTML被发明开始样式表就以各种形式出现了,不同的浏览器结合了它们各自的样式语言,读者可以使用这些样式语言来调节网页的显示方式。一开始样式表是给读者用的,最初的HTML版本只含有很少的显示属性,读者来决定网页应该怎样被显示。但随着HTML的成长,为了满足设计师的要求,HTML获得了很多显示功能。随着这些功能的增加外来定义样式的语言越来越没有意义了。1994</div>
                    </li>
                    <li><a href="/article/1899432100508004352.htm"
                           title="前端 UI 框架发展史" target="_blank">前端 UI 框架发展史</a>
                        <span class="text-muted">之道前端</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E7%9F%A5%E8%AF%86%E7%82%B9/1.htm">前端知识点</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/ui/1.htm">ui</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E7%A8%8B%E5%BA%8F%E4%BA%BA%E7%94%9F/1.htm">程序人生</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/1.htm">前端框架</a>
                        <div>上一小节我们了解了前端UI框架的作用和意义,接下来我们再来了解前端UI框架的发展历史。虽然是讲历史,但我不想讲得太复杂,也不打算搞什么编年史记录啥的,毕竟我们不是来学历史的。我会简单描述一下前端UI框架的发展历程,同时在这个过程中,把我自己的一些感受和想法分享给你。你可以以轻松娱乐的心态来看这篇文章,同时也大概了解一下我们前端开发是怎么发展到现在这样子的。这样可以让你更好地去理解将要学习的前端UI</div>
                    </li>
                    <li><a href="/article/1899430966909267968.htm"
                           title="Webpack 打包详细教程" target="_blank">Webpack 打包详细教程</a>
                        <span class="text-muted">oliver.chau</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/1.htm">前端开发</a><a class="tag" taget="_blank" href="/search/webpack/1.htm">webpack</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/node.js/1.htm">node.js</a>
                        <div>Webpack是一个现代JavaScript应用的静态模块打包工具,它可以处理JavaScript、CSS、图片等资源,并优化它们以提高性能。以下是Webpack从基础到进阶的详细教程。1.Webpack基础概念Webpack的核心概念包括:Entry(入口):Webpack开始打包的起点。Output(输出):打包后的文件存放路径。Loaders(加载器):转换非JavaScript资源(如CS</div>
                    </li>
                    <li><a href="/article/1899416342138777600.htm"
                           title="如何在PHP中实现API版本管理:保持向后兼容性" target="_blank">如何在PHP中实现API版本管理:保持向后兼容性</a>
                        <span class="text-muted">奥顺互联V</span>
<a class="tag" taget="_blank" href="/search/php/1.htm">php</a><a class="tag" taget="_blank" href="/search/php/1.htm">php</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>如何在PHP中实现API版本管理:保持向后兼容性在现代Web开发中,API(应用程序编程接口)是连接前端和后端的关键桥梁。随着业务需求的不断变化,API的版本管理变得尤为重要。良好的版本管理策略不仅能够确保新功能的顺利引入,还能保持向后兼容性,避免对现有客户端造成破坏性影响。本文将探讨如何在PHP中实现API版本管理,并保持向后兼容性。1.为什么需要API版本管理?API版本管理的主要目的是在不破</div>
                    </li>
                    <li><a href="/article/1899415080697655296.htm"
                           title="JS: 类型转换 + 运算符 + 循环" target="_blank">JS: 类型转换 + 运算符 + 循环</a>
                        <span class="text-muted">..儒</span>
<a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/ecmascript/1.htm">ecmascript</a>
                        <div>类型转换一,为什么需要类型转换JavaScript是弱数据类型:JavaScript也不知道变量到底属于那种数据类型,只有赋值了才清楚。坑:使用表单、prompt获取过来的数据默认是字符串类型的,此时就不能直接简单的进行加法运算。console.log('1000e'+‘2000')//输出结果100002000此时需要转换变量的数据类型。通俗来说,就是把一种数据类型的变量转换成我们需要的数据类型</div>
                    </li>
                    <li><a href="/article/1899414197138157568.htm"
                           title="Uniapp组件 Textarea 字数统计和限制" target="_blank">Uniapp组件 Textarea 字数统计和限制</a>
                        <span class="text-muted">weixin_42220130</span>
<a class="tag" taget="_blank" href="/search/uniapp/1.htm">uniapp</a><a class="tag" taget="_blank" href="/search/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/1.htm">微信小程序</a><a class="tag" taget="_blank" href="/search/uni-app/1.htm">uni-app</a><a class="tag" taget="_blank" href="/search/textarea/1.htm">textarea</a><a class="tag" taget="_blank" href="/search/%E8%BE%93%E5%85%A5%E6%A1%86/1.htm">输入框</a><a class="tag" taget="_blank" href="/search/%E7%BB%9F%E8%AE%A1/1.htm">统计</a><a class="tag" taget="_blank" href="/search/%E9%99%90%E5%88%B6/1.htm">限制</a>
                        <div>UniappTextarea字数统计和限制在Uniapp中,可以通过监听textarea的input事件来实现字数统计功能。以下是一个简单的示例,展示如何在textarea的右下角显示输入的字符数。示例代码首先,在模板中定义一个textarea元素,并绑定input事件处理函数:{{fontNum}}/200然后,在JavaScript部分定义updateFontNum方法来更新字符数:expor</div>
                    </li>
                    <li><a href="/article/1899412180600680448.htm"
                           title="Web端测试时,接口返回200,页面有没显示,可能时什么原因?" target="_blank">Web端测试时,接口返回200,页面有没显示,可能时什么原因?</a>
                        <span class="text-muted">海姐软件测试</span>
<a class="tag" taget="_blank" href="/search/%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7/1.htm">测试工具</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a><a class="tag" taget="_blank" href="/search/%E8%81%8C%E5%9C%BA%E5%92%8C%E5%8F%91%E5%B1%95/1.htm">职场和发展</a>
                        <div>需从系统架构、前后端交互、测试方法三个维度展开分析,结合具体场景给出可落地的排查方案:一、核心原因分析(按优先级排序)前端渲染异常JS脚本执行错误(如语法错误导致页面渲染中断)DOM元素未正确加载(XHR异步请求未完成时触发渲染)CSS样式冲突(display:none/visibility:hidden导致元素不可见)数据解析错误接口返回字段缺失(如缺少关键展示字段id)数据格式不符合预期(如字</div>
                    </li>
                    <li><a href="/article/1899411170629054464.htm"
                           title="如何有效管理 JavaScript 中的内存:垃圾回收与最佳实践" target="_blank">如何有效管理 JavaScript 中的内存:垃圾回收与最佳实践</a>
                        <span class="text-muted">名之以父</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E5%AE%89%E5%85%A8/1.htm">安全</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/1.htm">前端框架</a><a class="tag" taget="_blank" href="/search/react.js/1.htm">react.js</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a>
                        <div>“垃圾回收是现代编程语言的核心特性之一,它使得开发者可以专注于功能实现,而无需担心内存管理的细节。”——在JavaScript中,垃圾回收(GC)是一个自动化的内存管理过程,它帮助我们确保不再使用的内存得到释放。尽管JavaScript的垃圾回收机制非常强大,但如果对其原理和工作方式不够了解,也可能导致一些性能问题和内存泄漏。本文将深入探讨JavaScript中的垃圾回收机制、算法以及如何优化垃圾</div>
                    </li>
                    <li><a href="/article/1899411172239667200.htm"
                           title="【JavaScript 】垃圾回收机制进阶解析:提高性能的终极指南" target="_blank">【JavaScript 】垃圾回收机制进阶解析:提高性能的终极指南</a>
                        <span class="text-muted">名之以父</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/jvm/1.htm">jvm</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E5%AE%89%E5%85%A8/1.htm">安全</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a>
                        <div>“垃圾回收机制不仅是内存管理的基石,更是高效Web开发的保障。在JavaScript中,理解其工作原理至关重要。”在JavaScript中,垃圾回收(GarbageCollection,GC)是一个自动化的内存管理过程,能够有效防止内存泄漏虽然这看似是一个简单的机制,但背后却包含着丰富的理论与实现细节。理解这些原理,不仅能够帮助我们写出更高效的代码,还能避免一些性能问题和内存泄漏。本文将带你深入探</div>
                    </li>
                    <li><a href="/article/1899405119406010368.htm"
                           title="JavaScript面试宝典" target="_blank">JavaScript面试宝典</a>
                        <span class="text-muted">傻小胖</span>
<a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a>
                        <div>1.JS由哪三部分组成?JavaScript由以下三部分组成:ECMAScript(ES):JavaScript的核心语法,如变量、作用域、数据类型、函数、对象等。DOM(文档对象模型):用于操作HTML和XML文档的API,可以动态修改网页内容、结构和样式。BOM(浏览器对象模型):用于操作浏览器窗口和页面,例如window、navigator、location、history、screen等对</div>
                    </li>
                    <li><a href="/article/1899400197226688512.htm"
                           title="我与DeepSeek读《大型网站技术架构》(3)" target="_blank">我与DeepSeek读《大型网站技术架构》(3)</a>
                        <span class="text-muted">诺亚凹凸曼</span>
<a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a>
                        <div>大型网站架构的核心要素《大型网站技术架构:核心原理与案例分析》第三章聚焦于大型网站架构的核心要素,从技术维度剖析了构建高可用、高性能、可扩展系统的关键设计方向。1.五大核心架构要素(1)性能(Performance)目标:快速响应用户请求,优化用户体验。关键策略:前端优化:CDN加速静态资源、合并压缩JS/CSS、浏览器缓存。服务端优化:缓存(Redis/Memcached)、异步处理(消息队列)</div>
                    </li>
                    <li><a href="/article/1899397170772111360.htm"
                           title="JavaScript模块化开发的演进历程" target="_blank">JavaScript模块化开发的演进历程</a>
                        <span class="text-muted">IronKee</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a>
                        <div>写在前面的话js模块化历程记录了js模块化思想的诞生与变迁历史不是过去,历史正在上演,一切终究都会成为历史拥抱变化,面向未来延伸阅读-JavaScript诞生(这也解释了JS为何一开始没有模块化)JavaScript因为互联网而生,紧随着浏览器的出现而问世1990年底,欧洲核能研究组织(CERN)科学家Tim,发明了万维网(WorldWideWeb),最早的网页只能在操作系统的终端里浏览,非常不方</div>
                    </li>
                    <li><a href="/article/1899384937878974464.htm"
                           title="Dash 简介" target="_blank">Dash 简介</a>
                        <span class="text-muted">tankusa</span>
<a class="tag" taget="_blank" href="/search/dash/1.htm">dash</a>
                        <div>Dash是一个基于Python的开源框架,专门用于构建数据分析和数据可视化的Web应用程序。Dash由Plotly团队开发,旨在帮助数据分析师、数据科学家和开发人员快速创建交互式的、基于数据的Web应用,而无需深入掌握前端技术(如HTML、CSS和JavaScript)。Dash的核心优势在于其简单易用性和强大的功能。通过Dash,用户可以使用纯Python代码来构建复杂的Web应用,而无需编写繁</div>
                    </li>
                    <li><a href="/article/1899375098146648064.htm"
                           title="Zookeeper【概念(集中式到分布式、什么是分布式 、CAP定理 、什么是Zookeeper、应用场景、为什么选择Zookeeper 、基本概念) 】(一)-全面详解(学习总结---从入门到深化)" target="_blank">Zookeeper【概念(集中式到分布式、什么是分布式 、CAP定理 、什么是Zookeeper、应用场景、为什么选择Zookeeper 、基本概念) 】(一)-全面详解(学习总结---从入门到深化)</a>
                        <span class="text-muted">童小纯</span>
<a class="tag" taget="_blank" href="/search/%E4%B8%AD%E9%97%B4%E4%BB%B6%E5%A4%A7%E5%85%A8---%E5%85%A8%E9%9D%A2%E8%AF%A6%E8%A7%A3/1.htm">中间件大全---全面详解</a><a class="tag" taget="_blank" href="/search/zookeeper/1.htm">zookeeper</a><a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F/1.htm">分布式</a>
                        <div>作者简介:大家好,我是小童,Java开发工程师,CSDN博客博主,Java领域新星创作者系列专栏:前端、Java、Java中间件大全、微信小程序、微信支付、若依框架、Spring全家桶如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步如果感觉博主的文章还不错的话,请三连支持一下博主哦博主正在努力完成2023计划中:以梦为马,扬帆起航,2023追梦人目录Zookeeper概念_集中式到分布</div>
                    </li>
                    <li><a href="/article/1899366676525281280.htm"
                           title="Yarn:包管理优化与工作空间的最佳实践" target="_blank">Yarn:包管理优化与工作空间的最佳实践</a>
                        <span class="text-muted"></span>

                        <div>在现代前端开发中,包管理工具是不可或缺的工具之一。Yarn作为一个快速、可靠且安全的包管理工具,相对于npm,提供了一些独特的功能和优化,尤其是在工作空间管理和性能优化方面尤为突出。本文将深入探讨Yarn的专业使用,包括其工作空间的强大功能、性能优化技术以及在大型项目中的最佳实践。Yarn简介Yarn是由Facebook开发的一个JavaScript包管理工具,它旨在解决npm的一些关键问题,如安</div>
                    </li>
                    <li><a href="/article/1899361476049694720.htm"
                           title="数字IC前端设计究竟怎样?薪资前景如何?" target="_blank">数字IC前端设计究竟怎样?薪资前景如何?</a>
                        <span class="text-muted">IC观察者</span>
<a class="tag" taget="_blank" href="/search/fpga%E5%BC%80%E5%8F%91/1.htm">fpga开发</a><a class="tag" taget="_blank" href="/search/%E9%9B%86%E6%88%90%E7%94%B5%E8%B7%AF/1.htm">集成电路</a><a class="tag" taget="_blank" href="/search/%E6%A8%A1%E6%8B%9FIC/1.htm">模拟IC</a><a class="tag" taget="_blank" href="/search/%E6%A8%A1%E6%8B%9F%E7%89%88%E5%9B%BE/1.htm">模拟版图</a><a class="tag" taget="_blank" href="/search/%E6%A8%A1%E6%8B%9F%E7%89%88%E5%9B%BE%E5%85%A5%E9%97%A8/1.htm">模拟版图入门</a>
                        <div>数字ic前端岗位介绍:数字ic前端设计处于数字IC设计流程的前端,属于数字IC设计类岗位的一种。数字ic前端设计主要分成几种层次的设计:IPlevel,unitlevel,fullchip/SoClevel,gatelevel等。作为数字IC前端工程师,为了让写的RTL代码没有bug,会经常与验证工程师要求debugcase;为了了解芯片整体架构和功能属性,还要与架构工程师打交道;还要与后端工程师</div>
                    </li>
                    <li><a href="/article/1899360845343813632.htm"
                           title="ES6 解构赋值详解" target="_blank">ES6 解构赋值详解</a>
                        <span class="text-muted">修己xj</span>
<a class="tag" taget="_blank" href="/search/web/1.htm">web</a><a class="tag" taget="_blank" href="/search/es6/1.htm">es6</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/es6/1.htm">es6</a>
                        <div>ES6是JavaScript语言的一次重大更新,引入了许多新特性和语法改进,其中解构赋值是一个非常实用和灵活的语法特性。它可以让我们从数组或对象中提取值,并赋给对应的变量,让代码变得更加简洁和易读。本文将深入探讨ES6解构赋值的语法、用法及其在实际开发中的应用。数组解构赋值数组解构赋值允许我们通过类似模式匹配的方式,从数组中提取值并赋给变量,即只要等会两边的变量模式相同,左边的变量就会被赋予对应的</div>
                    </li>
                    <li><a href="/article/1899360717161689088.htm"
                           title="tauri + vue3 如何实现在一个页面上局部加载外部网页?" target="_blank">tauri + vue3 如何实现在一个页面上局部加载外部网页?</a>
                        <span class="text-muted">bug菌¹</span>
<a class="tag" taget="_blank" href="/search/%E5%85%A8%E6%A0%88Bug%E8%B0%83%E4%BC%98%28%E5%AE%9E%E6%88%98%E7%89%88%29/1.htm">全栈Bug调优(实战版)</a><a class="tag" taget="_blank" href="/search/%23/1.htm">#</a><a class="tag" taget="_blank" href="/search/CSDN%E9%97%AE%E7%AD%94%E8%A7%A3%E6%83%91%28%E5%85%A8%E6%A0%88%E7%89%88%29/1.htm">CSDN问答解惑(全栈版)</a><a class="tag" taget="_blank" href="/search/tauri/1.htm">tauri</a><a class="tag" taget="_blank" href="/search/vue3/1.htm">vue3</a>
                        <div>本文收录于「Bug调优」专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!问题描述  tauriv1(1.6左右)+vue3我想在vue3前端页面上在一个页面而不是window.open打开一个新的窗口去加载外部网页我想在一个页面中局部中间加载一个外部网页(试过</div>
                    </li>
                                <li><a href="/article/4.htm"
                                       title="java的(PO,VO,TO,BO,DAO,POJO)" target="_blank">java的(PO,VO,TO,BO,DAO,POJO)</a>
                                    <span class="text-muted">Cb123456</span>
<a class="tag" taget="_blank" href="/search/VO/1.htm">VO</a><a class="tag" taget="_blank" href="/search/TO/1.htm">TO</a><a class="tag" taget="_blank" href="/search/BO/1.htm">BO</a><a class="tag" taget="_blank" href="/search/POJO/1.htm">POJO</a><a class="tag" taget="_blank" href="/search/DAO/1.htm">DAO</a>
                                    <div>转: 
http://www.cnblogs.com/yxnchinahlj/archive/2012/02/24/2366110.html 
  
------------------------------------------------------------------- 
 O/R Mapping 是 Object Relational Mapping(对象关系映</div>
                                </li>
                                <li><a href="/article/131.htm"
                                       title="spring ioc原理(看完后大家可以自己写一个spring)" target="_blank">spring ioc原理(看完后大家可以自己写一个spring)</a>
                                    <span class="text-muted">aijuans</span>
<a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a>
                                    <div>         最近,买了本Spring入门书:spring In Action 。大致浏览了下感觉还不错。就是入门了点。Manning的书还是不错的,我虽然不像哪些只看Manning书的人那样专注于Manning,但怀着崇敬 的心情和激情通览了一遍。又一次接受了IOC 、DI、AOP等Spring核心概念。 先就IOC和DI谈一点我的看法。IO</div>
                                </li>
                                <li><a href="/article/258.htm"
                                       title="MyEclipse 2014中Customize Persperctive设置无效的解决方法" target="_blank">MyEclipse 2014中Customize Persperctive设置无效的解决方法</a>
                                    <span class="text-muted">Kai_Ge</span>
<a class="tag" taget="_blank" href="/search/MyEclipse2014/1.htm">MyEclipse2014</a>
                                    <div>高高兴兴下载个MyEclipse2014,发现工具条上多了个手机开发的按钮,心生不爽就想弄掉他!
结果发现Customize Persperctive失效!!
有说更新下就好了,可是国内Myeclipse访问不了,何谈更新...
so~这里提供了更新后的一下jar包,给大家使用! 
1、将9个jar复制到myeclipse安装目录\plugins中
2、删除和这9个jar同包名但是版本号较</div>
                                </li>
                                <li><a href="/article/385.htm"
                                       title="SpringMvc上传" target="_blank">SpringMvc上传</a>
                                    <span class="text-muted">120153216</span>
<a class="tag" taget="_blank" href="/search/springMVC/1.htm">springMVC</a>
                                    <div>  
@RequestMapping(value = WebUrlConstant.UPLOADFILE)
	@ResponseBody
	public Map<String, Object> uploadFile(HttpServletRequest request,HttpServletResponse httpresponse) {
		try {
			// </div>
                                </li>
                                <li><a href="/article/512.htm"
                                       title="Javascript----HTML DOM 事件" target="_blank">Javascript----HTML DOM 事件</a>
                                    <span class="text-muted">何必如此</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/Web/1.htm">Web</a>
                                    <div>HTML DOM 事件允许Javascript在HTML文档元素中注册不同事件处理程序。 
 
事件通常与函数结合使用,函数不会在事件发生前被执行! 
 
 
注:DOM: 指明使用的 DOM 属性级别。 
 
1.鼠标事件 
属性               </div>
                                </li>
                                <li><a href="/article/639.htm"
                                       title="动态绑定和删除onclick事件" target="_blank">动态绑定和删除onclick事件</a>
                                    <span class="text-muted">357029540</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a>
                                    <div>因为对JQUERY和JS的动态绑定事件的不熟悉,今天花了好久的时间才把动态绑定和删除onclick事件搞定!现在分享下我的过程。 
 
     在我的查询页面,我将我的onclick事件绑定到了tr标签上同时传入当前行(this值)参数,这样可以在点击行上的任意地方时可以选中checkbox,但是在我的某一列上也有一个onclick事件是用于下载附件的,当</div>
                                </li>
                                <li><a href="/article/766.htm"
                                       title="HttpClient|HttpClient请求详解" target="_blank">HttpClient|HttpClient请求详解</a>
                                    <span class="text-muted">7454103</span>
<a class="tag" taget="_blank" href="/search/apache/1.htm">apache</a><a class="tag" taget="_blank" href="/search/%E5%BA%94%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">应用服务器</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/1.htm">网络协议</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C%E5%BA%94%E7%94%A8/1.htm">网络应用</a><a class="tag" taget="_blank" href="/search/Security/1.htm">Security</a>
                                    <div>HttpClient 是 Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。本文首先介绍 HTTPClient,然后根据作者实际工作经验给出了一些常见问题的解决方法。HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需</div>
                                </li>
                                <li><a href="/article/893.htm"
                                       title="递归 逐层统计树形结构数据" target="_blank">递归 逐层统计树形结构数据</a>
                                    <span class="text-muted">darkranger</span>
<a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a>
                                    <div>将集合递归获取树形结构: 
 
 
/** 
 * 
 * 递归获取数据 
 * @param alist:所有分类 
 * @param subjname:对应统计的项目名称 
 * @param pk:对应项目主键 
 * @param reportList: 最后统计的结果集 
 * @param count:项目级别 
 */ 
 
 public void getReportVO(Arr</div>
                                </li>
                                <li><a href="/article/1020.htm"
                                       title="访问WEB-INF下使用frameset标签页面出错的原因" target="_blank">访问WEB-INF下使用frameset标签页面出错的原因</a>
                                    <span class="text-muted">aijuans</span>
<a class="tag" taget="_blank" href="/search/struts2/1.htm">struts2</a>
                                    <div><frameset rows="61,*,24" cols="*" framespacing="0" frameborder="no" border="0">          </div>
                                </li>
                                <li><a href="/article/1147.htm"
                                       title="MAVEN常用命令" target="_blank">MAVEN常用命令</a>
                                    <span class="text-muted">avords</span>

                                    <div>Maven库: 
http://repo2.maven.org/maven2/ 
Maven依赖查询: 
http://mvnrepository.com/ 
Maven常用命令: 1. 创建Maven的普通java项目:    mvn archetype:create    -DgroupId=packageName </div>
                                </li>
                                <li><a href="/article/1274.htm"
                                       title="PHP如果自带一个小型的web服务器就好了" target="_blank">PHP如果自带一个小型的web服务器就好了</a>
                                    <span class="text-muted">houxinyou</span>
<a class="tag" taget="_blank" href="/search/apache/1.htm">apache</a><a class="tag" taget="_blank" href="/search/%E5%BA%94%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">应用服务器</a><a class="tag" taget="_blank" href="/search/Web/1.htm">Web</a><a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a><a class="tag" taget="_blank" href="/search/%E8%84%9A%E6%9C%AC/1.htm">脚本</a>
                                    <div>最近单位用PHP做网站,感觉PHP挺好的,不过有一些地方不太习惯,比如,环境搭建。PHP本身就是一个网站后台脚本,但用PHP做程序时还要下载apache,配置起来也不太很方便,虽然有好多配置好的apache+php+mysq的环境,但用起来总是心里不太舒服,因为我要的只是一个开发环境,如果是真实的运行环境,下个apahe也无所谓,但只是一个开发环境,总有一种杀鸡用牛刀的感觉。如果php自己的程序中</div>
                                </li>
                                <li><a href="/article/1401.htm"
                                       title="NoSQL数据库之Redis数据库管理(list类型)" target="_blank">NoSQL数据库之Redis数据库管理(list类型)</a>
                                    <span class="text-muted">bijian1013</span>
<a class="tag" taget="_blank" href="/search/redis/1.htm">redis</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a><a class="tag" taget="_blank" href="/search/NoSQL/1.htm">NoSQL</a>
                                    <div>3.list类型及操作 
        List是一个链表结构,主要功能是push、pop、获取一个范围的所有值等等,操作key理解为链表的名字。Redis的list类型其实就是一个每个子元素都是string类型的双向链表。我们可以通过push、pop操作从链表的头部或者尾部添加删除元素,这样list既可以作为栈,又可以作为队列。 
  &nbs</div>
                                </li>
                                <li><a href="/article/1528.htm"
                                       title="谁在用Hadoop?" target="_blank">谁在用Hadoop?</a>
                                    <span class="text-muted">bingyingao</span>
<a class="tag" taget="_blank" href="/search/hadoop/1.htm">hadoop</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E6%8C%96%E6%8E%98/1.htm">数据挖掘</a><a class="tag" taget="_blank" href="/search/%E5%85%AC%E5%8F%B8/1.htm">公司</a><a class="tag" taget="_blank" href="/search/%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF/1.htm">应用场景</a>
                                    <div>Hadoop技术的应用已经十分广泛了,而我是最近才开始对它有所了解,它在大数据领域的出色表现也让我产生了兴趣。浏览了他的官网,其中有一个页面专门介绍目前世界上有哪些公司在用Hadoop,这些公司涵盖各行各业,不乏一些大公司如alibaba,ebay,amazon,google,facebook,adobe等,主要用于日志分析、数据挖掘、机器学习、构建索引、业务报表等场景,这更加激发了学习它的热情。</div>
                                </li>
                                <li><a href="/article/1655.htm"
                                       title="【Spark七十六】Spark计算结果存到MySQL" target="_blank">【Spark七十六】Spark计算结果存到MySQL</a>
                                    <span class="text-muted">bit1129</span>
<a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a>
                                    <div>package spark.examples.db

import java.sql.{PreparedStatement, Connection, DriverManager}

import com.mysql.jdbc.Driver
import org.apache.spark.{SparkContext, SparkConf}

object SparkMySQLInteg</div>
                                </li>
                                <li><a href="/article/1782.htm"
                                       title="Scala: JVM上的函数编程" target="_blank">Scala: JVM上的函数编程</a>
                                    <span class="text-muted">bookjovi</span>
<a class="tag" taget="_blank" href="/search/scala/1.htm">scala</a><a class="tag" taget="_blank" href="/search/erlang/1.htm">erlang</a><a class="tag" taget="_blank" href="/search/haskell/1.htm">haskell</a>
                                    <div>    说Scala是JVM上的函数编程一点也不为过,Scala把面向对象和函数型编程这两种主流编程范式结合了起来,对于熟悉各种编程范式的人而言Scala并没有带来太多革新的编程思想,scala主要的有点在于Java庞大的package优势,这样也就弥补了JVM平台上函数型编程的缺失,MS家.net上已经有了F#,JVM怎么能不跟上呢? 
    对本人而言</div>
                                </li>
                                <li><a href="/article/1909.htm"
                                       title="jar打成exe" target="_blank">jar打成exe</a>
                                    <span class="text-muted">bro_feng</span>
<a class="tag" taget="_blank" href="/search/java+jar+exe/1.htm">java jar exe</a>
                                    <div>今天要把jar包打成exe,jsmooth和exe4j都用了。 
遇见几个问题。记录一下。 
两个软件都很好使,网上都有图片教程,都挺不错。 
 
首先肯定是要用自己的jre的,不然不能通用,其次别忘了把需要的lib放到classPath中。 
困扰我很久的一个问题是,我自己打包成功后,在一个同事的没有装jdk的电脑上运行,就是不行,报错jvm.dll为无效的windows映像,如截图 
最后发现</div>
                                </li>
                                <li><a href="/article/2036.htm"
                                       title="读《研磨设计模式》-代码笔记-策略模式-Strategy" target="_blank">读《研磨设计模式》-代码笔记-策略模式-Strategy</a>
                                    <span class="text-muted">bylijinnan</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a>
                                    <div>声明: 本文只为方便我个人查阅和理解,详细的分析以及源代码请移步 原作者的博客http://chjavach.iteye.com/ 
 
 




/*
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化

简单理解:
1、将不同的策略提炼出一个共同接口。这是容易的,因为不同的策略,只是算法不同,需要传递的参数</div>
                                </li>
                                <li><a href="/article/2163.htm"
                                       title="cmd命令值cvfM命令" target="_blank">cmd命令值cvfM命令</a>
                                    <span class="text-muted">chenyu19891124</span>
<a class="tag" taget="_blank" href="/search/cmd/1.htm">cmd</a>
                                    <div>     cmd命令还真是强大啊。今天发现jar -cvfM aa.rar @aaalist 就这行命令可以根据aaalist取出相应的文件 
  例如: 
     在d:\workspace\prpall\test.java 有这样一个文件,现在想要将这个文件打成一个包。运行如下命令即可比如在d:\wor</div>
                                </li>
                                <li><a href="/article/2290.htm"
                                       title="OpenJWeb(1.8) Java Web应用快速开发平台" target="_blank">OpenJWeb(1.8) Java Web应用快速开发平台</a>
                                    <span class="text-muted">comsci</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E6%A1%86%E6%9E%B6/1.htm">框架</a><a class="tag" taget="_blank" href="/search/Web/1.htm">Web</a><a class="tag" taget="_blank" href="/search/%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86/1.htm">项目管理</a><a class="tag" taget="_blank" href="/search/%E4%BC%81%E4%B8%9A%E5%BA%94%E7%94%A8/1.htm">企业应用</a>
                                    <div>  
  OpenJWeb(1.8) Java Web应用快速开发平台的作者是我们技术联盟的成员,他最近推出了新版本的快速应用开发平台  OpenJWeb(1.8),我帮他做做宣传 
 
  OpenJWeb快速开发平台以快速开发为核心,整合先进的java 开源框架,本着自主开发+应用集成相结合的原则,旨在为政府、企事业单位、软件公司等平台用户提供一个架构透</div>
                                </li>
                                <li><a href="/article/2417.htm"
                                       title="Python 报错:IndentationError: unexpected indent" target="_blank">Python 报错:IndentationError: unexpected indent</a>
                                    <span class="text-muted">daizj</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/tab/1.htm">tab</a><a class="tag" taget="_blank" href="/search/%E7%A9%BA%E6%A0%BC/1.htm">空格</a><a class="tag" taget="_blank" href="/search/%E7%BC%A9%E8%BF%9B/1.htm">缩进</a>
                                    <div>    IndentationError: unexpected indent 是缩进的问题,也有可能是tab和空格混用啦 
 
    Python开发者有意让违反了缩进规则的程序不能通过编译,以此来强制程序员养成良好的编程习惯。并且在Python语言里,缩进而非花括号或者某种关键字,被用于表示语句块的开始和退出。增加缩进表示语句块的开</div>
                                </li>
                                <li><a href="/article/2544.htm"
                                       title="HttpClient 超时设置" target="_blank">HttpClient 超时设置</a>
                                    <span class="text-muted">dongwei_6688</span>
<a class="tag" taget="_blank" href="/search/httpclient/1.htm">httpclient</a>
                                    <div>HttpClient中的超时设置包含两个部分: 
1. 建立连接超时,是指在httpclient客户端和服务器端建立连接过程中允许的最大等待时间 
2. 读取数据超时,是指在建立连接后,等待读取服务器端的响应数据时允许的最大等待时间 
  
在HttpClient 4.x中如下设置: 
  
  
HttpClient httpclient = new DefaultHttpC</div>
                                </li>
                                <li><a href="/article/2671.htm"
                                       title="小鱼与波浪" target="_blank">小鱼与波浪</a>
                                    <span class="text-muted">dcj3sjt126com</span>

                                    <div>一条小鱼游出水面看蓝天,偶然间遇到了波浪。  小鱼便与波浪在海面上游戏,随着波浪上下起伏、汹涌前进。  小鱼在波浪里兴奋得大叫:“你每天都过着这么刺激的生活吗?简直太棒了。”  波浪说:“岂只每天过这样的生活,几乎每一刻都这么刺激!还有更刺激的,要有潮汐变化,或者狂风暴雨,那才是兴奋得心脏都会跳出来。”  小鱼说:“真希望我也能变成一个波浪,每天随着风雨、潮汐流动,不知道有多么好!”  很快,小鱼</div>
                                </li>
                                <li><a href="/article/2798.htm"
                                       title="Error Code: 1175 You are using safe update mode and you tried to update a table" target="_blank">Error Code: 1175 You are using safe update mode and you tried to update a table</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a>
                                    <div> 
 快速高效用:SET SQL_SAFE_UPDATES = 0;下面的就不要看了! 
 今日用MySQL Workbench进行数据库的管理更新时,执行一个更新的语句碰到以下错误提示: 
 Error Code: 1175 
 You are using safe update mode and you tried to update a table without a WHERE that </div>
                                </li>
                                <li><a href="/article/2925.htm"
                                       title="枚举类型详细介绍及方法定义" target="_blank">枚举类型详细介绍及方法定义</a>
                                    <span class="text-muted">gaomysion</span>
<a class="tag" taget="_blank" href="/search/enum/1.htm">enum</a><a class="tag" taget="_blank" href="/search/javaee/1.htm">javaee</a>
                                    <div>转发 
http://developer.51cto.com/art/201107/275031.htm 
 
枚举其实就是一种类型,跟int, char 这种差不多,就是定义变量时限制输入的,你只能够赋enum里面规定的值。建议大家可以看看,这两篇文章,《java枚举类型入门》和《C++的中的结构体和枚举》,供大家参考。 
 
枚举类型是JDK5.0的新特征。Sun引进了一个全新的关键字enum</div>
                                </li>
                                <li><a href="/article/3052.htm"
                                       title="Merge Sorted Array" target="_blank">Merge Sorted Array</a>
                                    <span class="text-muted">hcx2013</span>
<a class="tag" taget="_blank" href="/search/array/1.htm">array</a>
                                    <div>Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array. 
Note:You may assume that nums1 has enough space (size that is</div>
                                </li>
                                <li><a href="/article/3179.htm"
                                       title="Expression Language 3.0新特性" target="_blank">Expression Language 3.0新特性</a>
                                    <span class="text-muted">jinnianshilongnian</span>
<a class="tag" taget="_blank" href="/search/el+3.0/1.htm">el 3.0</a>
                                    <div>Expression Language 3.0表达式语言规范最终版从2013-4-29发布到现在已经非常久的时间了;目前如Tomcat 8、Jetty 9、GlasshFish 4已经支持EL 3.0。新特性包括:如字符串拼接操作符、赋值、分号操作符、对象方法调用、Lambda表达式、静态字段/方法调用、构造器调用、Java8集合操作。目前Glassfish 4/Jetty实现最好,对大多数新特性</div>
                                </li>
                                <li><a href="/article/3306.htm"
                                       title="超越算法来看待个性化推荐" target="_blank">超越算法来看待个性化推荐</a>
                                    <span class="text-muted">liyonghui160com</span>
<a class="tag" taget="_blank" href="/search/%E8%B6%85%E8%B6%8A%E7%AE%97%E6%B3%95%E6%9D%A5%E7%9C%8B%E5%BE%85%E4%B8%AA%E6%80%A7%E5%8C%96%E6%8E%A8%E8%8D%90/1.htm">超越算法来看待个性化推荐</a>
                                    <div>  
       一提到个性化推荐,大家一般会想到协同过滤、文本相似等推荐算法,或是更高阶的模型推荐算法,百度的张栋说过,推荐40%取决于UI、30%取决于数据、20%取决于背景知识,虽然本人不是很认同这种比例,但推荐系统中,推荐算法起的作用起的作用是非常有限的。 
      就像任何</div>
                                </li>
                                <li><a href="/article/3433.htm"
                                       title="写给Javascript初学者的小小建议" target="_blank">写给Javascript初学者的小小建议</a>
                                    <span class="text-muted">pda158</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a>
                                    <div>  一般初学JavaScript的时候最头痛的就是浏览器兼容问题。在Firefox下面好好的代码放到IE就不能显示了,又或者是在IE能正常显示的代码在firefox又报错了。     如果你正初学JavaScript并有着一样的处境的话建议你:初学JavaScript的时候无视DOM和BOM的兼容性,将更多的时间花在 了解语言本身(ECMAScript)。只在特定浏览器编写代码(Chrome/Fi</div>
                                </li>
                                <li><a href="/article/3560.htm"
                                       title="Java 枚举" target="_blank">Java 枚举</a>
                                    <span class="text-muted">ShihLei</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/enum/1.htm">enum</a><a class="tag" taget="_blank" href="/search/%E6%9E%9A%E4%B8%BE/1.htm">枚举</a>
                                    <div>注:文章内容大量借鉴使用网上的资料,可惜没有记录参考地址,只能再传对作者说声抱歉并表示感谢! 
  
一 基础  1)语法 
  
 
      枚举类型只能有私有构造器(这样做可以保证客户代码没有办法新建一个enum的实例) 
      枚举实例必须最先定义 
   2)特性   
 
     &nb</div>
                                </li>
                                <li><a href="/article/3687.htm"
                                       title="Java SE 6 HotSpot虚拟机的垃圾回收机制" target="_blank">Java SE 6 HotSpot虚拟机的垃圾回收机制</a>
                                    <span class="text-muted">uuhorse</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/HotSpot/1.htm">HotSpot</a><a class="tag" taget="_blank" href="/search/GC/1.htm">GC</a><a class="tag" taget="_blank" href="/search/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/1.htm">垃圾回收</a><a class="tag" taget="_blank" href="/search/VM/1.htm">VM</a>
                                    <div>官方资料,关于Java SE 6 HotSpot虚拟机的garbage Collection,非常全,英文。 
http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html 
  Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning 
&</div>
                                </li>
                </ul>
            </div>
        </div>
    </div>

<div>
    <div class="container">
        <div class="indexes">
            <strong>按字母分类:</strong>
            <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a
                href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a
                href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a
                href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a
                href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a
                href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a
                href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a
                href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a
                href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a>
        </div>
    </div>
</div>
<footer id="footer" class="mb30 mt30">
    <div class="container">
        <div class="footBglm">
            <a target="_blank" href="/">首页</a> -
            <a target="_blank" href="/custom/about.htm">关于我们</a> -
            <a target="_blank" href="/search/Java/1.htm">站内搜索</a> -
            <a target="_blank" href="/sitemap.txt">Sitemap</a> -
            <a target="_blank" href="/custom/delete.htm">侵权投诉</a>
        </div>
        <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved.
<!--            <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>-->
        </div>
    </div>
</footer>
<!-- 代码高亮 -->
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script>
<link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/>
<script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script>





</body>

</html>