【前端工程化】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/1903955269470187520.htm"
                           title="包管理工具" target="_blank">包管理工具</a>
                        <span class="text-muted">她的双马尾</span>
<a class="tag" taget="_blank" href="/search/JS/1.htm">JS</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%8C%85%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7/1.htm">包管理工具</a><a class="tag" taget="_blank" href="/search/npm/1.htm">npm</a><a class="tag" taget="_blank" href="/search/yarn/1.htm">yarn</a><a class="tag" taget="_blank" href="/search/pnpm/1.htm">pnpm</a>
                        <div>JavaScript包管理工具对比:npm、yarn和pnpm1.npm1.1历史与背景npm(NodePackageManager)是Node.js的默认包管理工具,首次发布于2010年。它是JavaScript生态系统中最早的包管理工具,主要用于管理和共享JavaScript模块。目前,npm拥有全球最大的JavaScript包注册中心(npmregistry),包含数百万个开源包。1.2核心</div>
                    </li>
                    <li><a href="/article/1903952495978278912.htm"
                           title="uniapp" target="_blank">uniapp</a>
                        <span class="text-muted">Deepsleep.</span>
<a class="tag" taget="_blank" href="/search/uni-app/1.htm">uni-app</a>
                        <div>uni-app是一个使用Vue.js开发所有前端应用的框架,可以编译到iOS、Android、H5、以及各种小程序等多个平台。以下是uni-app页面生命周期的详细介绍,包括一些简单的示例:初始化阶段onLoad(options)触发时机:页面加载时触发,且只触发一次。参数:options是一个包含页面路径参数的对象。示例:从上一个页面传递参数到当前页面。onLoad(options){conso</div>
                    </li>
                    <li><a href="/article/1903944039179284480.htm"
                           title="若依框架二次开发——启动 RuoYi-Cloud 微服务项目" target="_blank">若依框架二次开发——启动 RuoYi-Cloud 微服务项目</a>
                        <span class="text-muted">bjzhang75</span>
<a class="tag" taget="_blank" href="/search/%E9%A1%B9%E7%9B%AE%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5/1.htm">项目开发实践</a><a class="tag" taget="_blank" href="/search/%E5%BE%AE%E6%9C%8D%E5%8A%A1/1.htm">微服务</a><a class="tag" taget="_blank" href="/search/%E8%8B%A5%E4%BE%9D/1.htm">若依</a>
                        <div>文章目录前期准备第一步:拉取RuoYi-Cloud项目源码第二步:初始化数据库1.创建数据库2.导入数据第三步:配置Nacos并启用持久化1.下载并解压Nacos2.启动Nacos3.访问Nacos控制台第四步:安装并运行Redis1.安装Redis2.启动Redis第五步:修改后端配置第六步:启动后端服务第七步:启动前端项目1.进入前端项目目录2.安装前端依赖3.启动前端第八步:访问系统总结Ru</div>
                    </li>
                    <li><a href="/article/1903941636459655168.htm"
                           title="前端实例:轮播图效果" target="_blank">前端实例:轮播图效果</a>
                        <span class="text-muted">2301_81535770</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a>
                        <div>利用HTML、CSS和JavaScript实现轮播图效果。一、轮播图原理:通过给窗口设置position属性和overflow属性,使得超出窗口范围的部分被隐藏,表面可见范围只包含窗口,但实际上其内部空间很大;调整胶卷相对于窗口的位置,使得整个胶卷向左移动;调用JS中的定时器,实现轮播效果。流程图如下:二、实现自动切换效果1、HTML搭建基础框架分为图片展示窗口和上下页切换按键两部分>2、CSS设</div>
                    </li>
                    <li><a href="/article/1903935077516111872.htm"
                           title="Angular与Laravel的CSRF问题探讨与解决" target="_blank">Angular与Laravel的CSRF问题探讨与解决</a>
                        <span class="text-muted">t0_54manong</span>
<a class="tag" taget="_blank" href="/search/%E7%BC%96%E7%A8%8B%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E6%89%8B%E5%86%8C/1.htm">编程问题解决手册</a><a class="tag" taget="_blank" href="/search/angular.js/1.htm">angular.js</a><a class="tag" taget="_blank" href="/search/laravel/1.htm">laravel</a><a class="tag" taget="_blank" href="/search/csrf/1.htm">csrf</a><a class="tag" taget="_blank" href="/search/%E4%B8%AA%E4%BA%BA%E5%BC%80%E5%8F%91/1.htm">个人开发</a>
                        <div>在现代Web开发中,安全性是一个不容忽视的关键问题。跨站请求伪造(CSRF)攻击是常见的安全威胁之一,幸运的是,Laravel框架已经为我们提供了强大的CSRF保护机制。然而,当我们将Angular前端与Laravel后端集成时,可能会遇到一些CSRF相关的挑战。今天我们将通过一个具体的案例来探讨如何解决Angular与Laravel之间的CSRF问题。背景介绍假设我们有一个使用Angular开发</div>
                    </li>
                    <li><a href="/article/1903933186770006016.htm"
                           title="前端请求怎么发送到后端:深度剖析与实用指南" target="_blank">前端请求怎么发送到后端:深度剖析与实用指南</a>
                        <span class="text-muted">dhfnngte24fhfn</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/django/1.htm">django</a><a class="tag" taget="_blank" href="/search/pygame/1.htm">pygame</a><a class="tag" taget="_blank" href="/search/virtualenv/1.htm">virtualenv</a>
                        <div>前端请求怎么发送到后端:深度剖析与实用指南在web开发中,前端与后端之间的通信是至关重要的。前端通过发送请求来获取后端的数据或执行某些操作,而后端则负责处理这些请求并返回相应的响应。本文将分四个方面、五个方面、六个方面和七个方面,深入剖析前端请求是如何发送到后端的,并为你提供实用的指南。四个方面:请求与响应的基础首先,我们需要了解前端请求与后端响应的基础概念。前端通过HTTP协议向后端发送请求,后</div>
                    </li>
                    <li><a href="/article/1903922839744999424.htm"
                           title="SpringMVC-解决跨域的两种方案" target="_blank">SpringMVC-解决跨域的两种方案</a>
                        <span class="text-muted">青岛欢迎您</span>
<a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E6%A1%86%E6%9E%B6/1.htm">开发框架</a><a class="tag" taget="_blank" href="/search/springmvc/1.htm">springmvc</a>
                        <div>1.什么是跨域跨域,即跨站HTTP请求(Cross-siteHTTPrequest),指发起请求的资源所在域不同于请求指向资源所在域的HTTP请求。2.跨域的应用情景当使用前后端分离,后端主导的开发方式进行前后端协作开发时,常常有如下情景:后端开发完毕在服务器上进行部署并给前端API文档。前端在本地进行开发并向远程服务器上部署的后端发送请求。在这种开发过程中,如果前端想要一边开发一边测试接口,就需</div>
                    </li>
                    <li><a href="/article/1903913757034213376.htm"
                           title="Vue——Vue-cli脚手架+前端路由" target="_blank">Vue——Vue-cli脚手架+前端路由</a>
                        <span class="text-muted">pdsu_zhao</span>
<a class="tag" taget="_blank" href="/search/Vue/1.htm">Vue</a><a class="tag" taget="_blank" href="/search/Vue%E5%AD%A6%E4%B9%A0%E4%B9%8B%E6%97%85/1.htm">Vue学习之旅</a><a class="tag" taget="_blank" href="/search/vue/1.htm">vue</a><a class="tag" taget="_blank" href="/search/v-router/1.htm">v-router</a><a class="tag" taget="_blank" href="/search/v-resource/1.htm">v-resource</a><a class="tag" taget="_blank" href="/search/vue-cli/1.htm">vue-cli</a><a class="tag" taget="_blank" href="/search/ES6/1.htm">ES6</a>
                        <div>Vue-cli是Vue的脚手架工具可以进行目录结构、本地调试、代码部署、热加载、单元测试1、MVVM框架View——ViewModel——Model(视图)(通讯)(数据)“DOM”“观察者vue实例”“Javascript”注意:交互为双向的特点:(1)针对具有复杂交互逻辑的前端应用;(2)提供基础的架构抽象;(3)通过Ajax数据持久化,保证前端用户体验。2、什么是Vue.js它是一个轻量级M</div>
                    </li>
                    <li><a href="/article/1903906566793392128.htm"
                           title="Angular与ASP.NET Core:解决表单数据传输问题" target="_blank">Angular与ASP.NET Core:解决表单数据传输问题</a>
                        <span class="text-muted">t0_54coder</span>
<a class="tag" taget="_blank" href="/search/%E7%BC%96%E7%A8%8B%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3%E6%89%8B%E5%86%8C/1.htm">编程问题解决手册</a><a class="tag" taget="_blank" href="/search/angular.js/1.htm">angular.js</a><a class="tag" taget="_blank" href="/search/asp.net/1.htm">asp.net</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E4%B8%AA%E4%BA%BA%E5%BC%80%E5%8F%91/1.htm">个人开发</a>
                        <div>在现代Web开发中,Angular和ASP.NETCore是两个非常流行的框架,它们的组合可以构建出高效且易于维护的应用程序。然而,在使用Angular发送表单数据到ASP.NETCoreAPI时,开发者常常会遇到一些数据传输的问题。今天我们就来探讨如何正确地处理这种情况,并通过实际例子来展示解决方案。问题描述假设我们有一个Angular前端应用,需要将一个包含文件和其他数据的表单提交到ASP.N</div>
                    </li>
                    <li><a href="/article/1903906440280600576.htm"
                           title="2021-最新Web前端经典面试试题及答案-史上最全前端面试题(含答案)---React篇" target="_blank">2021-最新Web前端经典面试试题及答案-史上最全前端面试题(含答案)---React篇</a>
                        <span class="text-muted">圆白菜和大白菜</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/react/1.htm">react</a><a class="tag" taget="_blank" href="/search/%E5%A4%A7%E5%89%8D%E7%AB%AF/1.htm">大前端</a><a class="tag" taget="_blank" href="/search/react/1.htm">react</a>
                        <div>★★★React事件绑定原理★★★React中的setState缺点是什么呢★★★React组件通信如何实现★★★类组件和函数组件的区别★★★请你说说React的路由是什么?★★★★★React有哪些性能优化的手段?★★★★Reacthooks用过吗,为什么要用?★★★★虚拟DOM的优劣如何?实现原理?★★★★React和Vue的diff时间复杂度从O(n^3)优化到O(n),那么O(n^3)和O</div>
                    </li>
                    <li><a href="/article/1903898742252171264.htm"
                           title="深入浅出:序列化与反序列化的全面解析" target="_blank">深入浅出:序列化与反序列化的全面解析</a>
                        <span class="text-muted">进击的小白菜</span>
<a class="tag" taget="_blank" href="/search/%E4%B8%80%E4%BA%9B%E5%BC%80%E5%8F%91%E5%B8%B8%E8%AF%86/1.htm">一些开发常识</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%BC%80%E5%8F%91%E5%B8%B8%E8%AF%86/1.htm">开发常识</a>
                        <div>文章目录1.引言2.什么是序列化?2.1为什么需要序列化?3.什么是反序列化?3.1反序列化的重要性4.序列化与反序列化的实现4.1JSON(JavaScriptObjectNotation)4.2XML(eXtensibleMarkupLanguage)4.3ProtocolBuffers(Protobuf)4.4MessagePack5.安全性考虑6.性能优化7.结论附录:常见问题解答Q1:什</div>
                    </li>
                    <li><a href="/article/1903896346037907456.htm"
                           title="Android :实现登录功能的思路" target="_blank">Android :实现登录功能的思路</a>
                        <span class="text-muted">前期后期</span>
<a class="tag" taget="_blank" href="/search/android/1.htm">android</a>
                        <div>android的登录功能和前端一样,需要保存登录的用户信息。创建一个工具类//用户工具类,用于管理用户登录状态和用户信息objectAppUserUtil{//常量定义privateconstvalLOGGED_FLAG="logged_flag"//登录状态的键名privateconstvalUSER_INFO="user_info"//用户信息的键名privateconstvalTAG="Ap</div>
                    </li>
                    <li><a href="/article/1903883482262728704.htm"
                           title="Electron打包文件生成.exe文件打开即可使用" target="_blank">Electron打包文件生成.exe文件打开即可使用</a>
                        <span class="text-muted">糕冷小美n</span>
<a class="tag" taget="_blank" href="/search/electron/1.htm">electron</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>1、Electron打包,包括需要下载的内容和环境配置步骤注意:Electron是一个使用JavaScript、HTML和CSS构建跨平台桌面应用程序的框架首先需要电脑环境有Node.js和npm我之前的文章有关nvm下载node的说明也可以去官网下载检查是否有node和npm环境命令node-vnpm-v输出版本号,说明安装成功2、创建Electron项目2.1创建项目目录打开命令行工具,创建一</div>
                    </li>
                    <li><a href="/article/1903883355305340928.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%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>在现代Web开发中,事件处理是实现动态和交互式网页的核心。当用户与页面进行交互时(如点击按钮、提交表单等),浏览器会生成相应的事件。为了有效地响应这些事件,JavaScript提供了事件对象,它包含了关于事件的详细信息。本文将详细介绍事件对象的概念、重要的属性和方法,并通过实例展示其应用场景。一、什么是事件对象?每当一个事件被触发时,浏览器都会创建一个事件对象,这个对象包含了该事件的所有相关信息,</div>
                    </li>
                    <li><a href="/article/1903876660910944256.htm"
                           title="Three.js世界中的三要素:场景、相机、渲染器" target="_blank">Three.js世界中的三要素:场景、相机、渲染器</a>
                        <span class="text-muted">Front_Yue</span>
<a class="tag" taget="_blank" href="/search/3D%E6%8A%80%E6%9C%AF%E5%AE%9E%E8%B7%B5%E6%8C%87%E5%8D%97/1.htm">3D技术实践指南</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/three.js/1.htm">three.js</a><a class="tag" taget="_blank" href="/search/3d/1.htm">3d</a>
                        <div>一、Three.js简介Three.js是一个基于WebGL的JavaScript库,它允许开发者在网页上创建和显示复杂的3D图形和动画,而无需用户安装任何额外的插件或软件。Three.js在Web开发中的地位非常重要,它通过提供简单直观的API,极大地降低了3D图形开发的门槛,使得开发者可以更专注于实现创意。Three.js广泛应用于游戏开发、虚拟现实、数据可视化、艺术创作等多个领域。二、场景:</div>
                    </li>
                    <li><a href="/article/1903873762076454912.htm"
                           title="PHP框架为基础的购物平台设计思路分步骤说明" target="_blank">PHP框架为基础的购物平台设计思路分步骤说明</a>
                        <span class="text-muted">星糖曙光</span>
<a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF%E8%AF%AD%E8%A8%80%EF%BC%88node/1.htm">后端语言(node</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/vue%E7%AD%89%E7%AD%89%EF%BC%89/1.htm">vue等等)</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E8%AF%BE%E7%A8%8B%E8%AE%BE%E8%AE%A1/1.htm">课程设计</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/php/1.htm">php</a>
                        <div>以下是以PHP框架为基础的购物平台设计思路分步骤说明:一、技术选型阶段技术栈={后端框架:Laravel/Yii2(提供ORM、路由、中间件支持)前端框架:Vue.js/React(可选SPA方案)数据库:MySQL8.0+(事务型数据存储)缓存:Redis(会话/商品缓存)队列:RabbitMQ(异步处理订单)\text{技术栈}=\begin{cases}后端框架:Laravel/Yii2(提</div>
                    </li>
                    <li><a href="/article/1903873756976181248.htm"
                           title="致现在的我与未来的我:编程长河中的摆渡手札" target="_blank">致现在的我与未来的我:编程长河中的摆渡手札</a>
                        <span class="text-muted">星糖曙光</span>
<a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF%E8%AF%AD%E8%A8%80%EF%BC%88node/1.htm">后端语言(node</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/vue%E7%AD%89%E7%AD%89%EF%BC%89/1.htm">vue等等)</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/1.htm">深度学习</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a>
                        <div>致现在的我与未来的我:编程长河中的摆渡手札一、技术积累:从萤火微光到星河初现(约3000字)前端的启蒙:HTML/CSS与"所见即所得"的魔法“代码是诗,但诗未必能成为产品”,初学编程时,我如《禅与摩托车维修艺术》中追寻"良质"的探索者,在W3School的教程中笨拙地敲下第一行。记得仿写京东首页时,一个浮动布局的错位让我通宵调试,最终发现竟是未闭合的标签——这让我想起《代码大全》中的警示:“计算</div>
                    </li>
                    <li><a href="/article/1903872748673888256.htm"
                           title="若依集成knife4j实现swagger文档增强" target="_blank">若依集成knife4j实现swagger文档增强</a>
                        <span class="text-muted">Roc-xb</span>
<a class="tag" taget="_blank" href="/search/knife4j/1.htm">knife4j</a>
                        <div>knife4j的前身是swagger-bootstrap-ui,为了契合微服务的架构发展,由于原来swagger-bootstrap-ui采用的是后端Java代码+前端Ui混合打包的方式,在微服务架构下显的很臃肿,因此项目正式更名为knife4j。目录一、单体版本1、ruoyi-admin\pom.xml模块添加整合依赖2、SwaggerController.java修改跳转访问地址二、前后端分离</div>
                    </li>
                    <li><a href="/article/1903866571588169728.htm"
                           title="C++在线OJ负载均衡项目" target="_blank">C++在线OJ负载均衡项目</a>
                        <span class="text-muted">平凡的小y</span>
<a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>1.演示项目项目源码链接:2.项目所用技术和开发环境所用技术C++STL标准库Boost准标准库(字符串切割)cpp-httplib第三方开源网络库ctemplate第三方开源前端网页渲染库jsoncpp第三方开源序列化、反序列化库负载均衡设计MySQLCconnectAce前端在线编辑器html/css/js/jquery/ajax开发环境Ubuntu云服务器vscodeMysqlWorkben</div>
                    </li>
                    <li><a href="/article/1903850929363415040.htm"
                           title="JavaScript基础-DOM的一些基本常用语法" target="_blank">JavaScript基础-DOM的一些基本常用语法</a>
                        <span class="text-muted">Southern Wind</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>
                        <div>总结了一下JS一直到DOM中所用的单词的用法输入方式:window.prompt('请输入数据');输出方式:1、window.alert('HelloJavaScript');2、console.log输出到控制台3、输出数据到页面document.write('hello')JavaScript数据类型1、基本类型string:字符型number:数值型boolean:布尔型2、特殊类型und</div>
                    </li>
                    <li><a href="/article/1903843363333926912.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%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>在现代Web开发中,动态地添加和移除事件处理器是构建交互式网页的关键技能之一。虽然添加事件处理器相对直观,但了解如何有效地移除或“解绑”这些处理器同样重要。这不仅有助于优化性能,还能防止潜在的内存泄漏问题。本文将介绍几种方法来删除JavaScript中的事件处理器,并探讨它们的应用场景及最佳实践。一、为什么需要删除事件?随着页面复杂度的增加,不恰当地管理事件处理器可能会导致性能下降或出现意外行为。</div>
                    </li>
                    <li><a href="/article/1903830758372470784.htm"
                           title="rocketmq-client 4.3.0 在springboot中的使用" target="_blank">rocketmq-client 4.3.0 在springboot中的使用</a>
                        <span class="text-muted">Myueye</span>
<a class="tag" taget="_blank" href="/search/JAVA/1.htm">JAVA</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                        <div>rocketmq-client4.3.0在springboot中的使用1、导入依赖2、配置文件属性3、编写配置类4、使用测试5、结果5.1RocketMQ后台显示5.2前端页面5.3后端后台1、导入依赖org.apache.rocketmqrocketmq-client4.3.02、配置文件属性mq.nameserverAdd=ip地址:9876mq.topic=top1(topic名称)mq.p</div>
                    </li>
                    <li><a href="/article/1903822687952760832.htm"
                           title="六十天前端强化训练之第二十九天之深入解析:从零构建企业级Vue项目的完整指南" target="_blank">六十天前端强化训练之第二十九天之深入解析:从零构建企业级Vue项目的完整指南</a>
                        <span class="text-muted">编程星辰海</span>
<a class="tag" taget="_blank" href="/search/%23/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%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/Vue%E9%A1%B9%E7%9B%AE/1.htm">Vue项目</a>
                        <div>=====欢迎来到编程星辰海的博客讲解======看完可以给一个免费的三连吗,谢谢大佬!目录一、Vite核心原理与开发优势二、项目创建深度解析三、配置体系深度剖析四、企业级项目架构设计五、性能优化实战六、开发提效技巧七、质量保障体系八、扩展阅读推荐一、Vite核心原理与开发优势1.1为什么选择Vite?Vite采用现代浏览器原生ES模块系统(NativeESM)作为开发服务器,颠覆了传统打包工具的</div>
                    </li>
                    <li><a href="/article/1903811454289637376.htm"
                           title="HTTP核心知识" target="_blank">HTTP核心知识</a>
                        <span class="text-muted">Sean2077</span>
<a class="tag" taget="_blank" href="/search/HTTP/1.htm">HTTP</a><a class="tag" taget="_blank" href="/search/http/1.htm">http</a>
                        <div>理解HTTP协议是优化Web应用性能、调试问题和实现高效通信的基础。以下是前端开发者需要掌握的核心HTTP知识:1.HTTP基础概念请求与响应模型理解客户端(浏览器)发送HTTP请求,服务器返回HTTP响应的基本流程。HTTP方法(Methods)GET:获取资源(幂等操作)POST:提交数据(非幂等)PUT:更新资源DELETE:删除资源HEAD:仅获取响应头OPTIONS:查看服务器支持的通信</div>
                    </li>
                    <li><a href="/article/1903809437764743168.htm"
                           title="前端性能优化-知识点" target="_blank">前端性能优化-知识点</a>
                        <span class="text-muted">甲亿</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/1.htm">性能优化</a>
                        <div>Web性能优化意义1.减少整体加载时间:减小文件体积、减少HTTP请求、使用预加载。2.使网站尽快可用:仅加载首屏内容,其他内容根据需要进行懒加载。3.平滑和交互性:使用CSS替代JS动画、减少UI重绘。4.加载表现形式:使用加载动画、进度条、骨架屏等过渡信息,让用户感觉到页面加载更快。5.性能监测:性能指标、性能测试、性能监控持续优化等Web性能指标RAIL性能模型Response(响应):快速</div>
                    </li>
                    <li><a href="/article/1903808555442565120.htm"
                           title="JavaScript 性能优化实战:优化循环结构提升效率" target="_blank">JavaScript 性能优化实战:优化循环结构提升效率</a>
                        <span class="text-muted">deying0865423</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>
                        <div>目录一、理解循环的性能损耗二、减少循环迭代次数(一)缓存数组长度(二)提前终止循环三、优化循环内部操作(一)避免在循环内执行复杂计算(二)减少DOM操作四、选择合适的循环类型(一)for循环与while循环的选择(二)for...in与for...of的使用场景在JavaScript编程中,循环结构是实现重复执行任务的基础工具。然而,不当的循环使用常常会导致性能瓶颈,特别是在处理大量数据时,循环的</div>
                    </li>
                    <li><a href="/article/1903804775091204096.htm"
                           title="【网络安全 | 漏洞挖掘】通过控制台调试实现登录" target="_blank">【网络安全 | 漏洞挖掘】通过控制台调试实现登录</a>
                        <span class="text-muted">秋说</span>
<a class="tag" taget="_blank" href="/search/web%E5%AE%89%E5%85%A8/1.htm">web安全</a><a class="tag" taget="_blank" href="/search/%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98/1.htm">漏洞挖掘</a>
                        <div>未经许可,不得转载。文章目录正文在安全测试过程中,我留意到一个特殊现象:当登录出现错误时,相关请求包并不经过BurpSuite。那么此时账号密码是储存在前端的,我通过调试即可实现登录管理员账户。正文由于系统设定,输入错误的账号和密码会弹出“账号密码错误”的提示。基于此,我在代码中“账号密码错误”提示的相关位置设置了断点,截图如下:随后,我刷新浏览器页面,输入错误的账号和密码,然后点击登录按钮,操作</div>
                    </li>
                    <li><a href="/article/1903794558131564544.htm"
                           title="vant官网-vant ui 首页-移动端Vue组件库" target="_blank">vant官网-vant ui 首页-移动端Vue组件库</a>
                        <span class="text-muted">embelfe_segge</span>
<a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF/1.htm">学习路线</a><a class="tag" taget="_blank" href="/search/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4/1.htm">阿里巴巴</a><a class="tag" taget="_blank" href="/search/android/1.htm">android</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%90%8E%E7%AB%AF/1.htm">后端</a>
                        <div>Vant是有赞前端团队开源的移动端vue组件库,适用于手机端h5页面。鉴于百度搜索不到vant官方网址,分享一下vant组件库官网地址,方便新手使用vant官网地址https://vant-contrib.gitee.io/vant/#/zh-CN/通过npm安装在现有项目中使用Vant时,可以通过npm或yarn进行安装:#Vue2项目,安装Vant2:npmivant-S#Vue3项目,安装V</div>
                    </li>
                    <li><a href="/article/1903788508699488256.htm"
                           title="可视化埋点在React Native中的实践" target="_blank">可视化埋点在React Native中的实践</a>
                        <span class="text-muted">Shopee技术团队</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/react/1.htm">react</a><a class="tag" taget="_blank" href="/search/native/1.htm">native</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/react.js/1.htm">react.js</a>
                        <div>本文首发于微信公众号“Shopee技术团队”。1.背景笔者所在团队为Shopee的本地生活前端团队,用户可以在我们的平台购买优惠券,然后去线下门店使用。随着用户规模不断增加,研究用户行为数据可以更好地指导产品功能设计,提供更加优秀的用户体验。用户行为数据的研究首先涉及到如何采集,即我们常说的“埋点”。一直以来,我们项目中的埋点都采用代码埋点,每次新增埋点往往是一些重复性的工作,且需要重新发布代码才</div>
                    </li>
                    <li><a href="/article/1903788381238784000.htm"
                           title="去哪儿网 ReactNative 跨小程序多端方案介绍" target="_blank">去哪儿网 ReactNative 跨小程序多端方案介绍</a>
                        <span class="text-muted">去哪儿网技术沙龙</span>
<a class="tag" taget="_blank" href="/search/%E5%A4%A7%E5%89%8D%E7%AB%AF/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/react/1.htm">react</a><a class="tag" taget="_blank" href="/search/native/1.htm">native</a><a class="tag" taget="_blank" href="/search/%E5%B0%8F%E7%A8%8B%E5%BA%8F/1.htm">小程序</a>
                        <div>1前言qrn-remax-unir是由去哪儿网前端技术团队实现的一套将RN适配到小程序端的跨端组件,通过该组件库可快速方便的将RN源代码直接运行到小程序端。方案参考了react-native-web的适配方案,使用remax框架来实现适配组件库并达到适配多小程序的目的。和react-native-web一样,它对RN源代码侵入度低,并且调试和替换组件相当方便。方案来自于社区,我们只是合理的应用用来</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>