前端工程化:模块化、包管理工具、打包工具(Webpack基本使用和优化)、前端性能监控

news2024/11/27 16:40:27

目录

  • 1、模块化
    • 1. CommonJS/AMD/CMD
      • 1.1 背景
      • 1.2 CommonJS规范的核心变量
      • 1.3 exports(module.exports)和require本质
      • 1.4 exports和module.exports的关系/区别
      • 1.5 实际开发用:module.exports = {}
      • 1.6 require(X)的查找规则
        • (1)X是一个Node核心模块,例如path等
        • (2)X是以 ./ 或 ../ 或 /(根目录)开头的
        • (3)直接是一个X(没有路径),并且X不是一个核心模块
      • 1.7 CommonJS的缺点
    • 2. ESModule
      • 2.1 背景
      • 2.2 ESModule的核心变量
      • 2.3 ESModule的导入/导出方式
        • 2种导出方式
        • 3种导入方式
      • 2.4 export和import结合使用
      • 2.4 default默认导出
        • 默认导出方式
        • 如何导入
      • 2.5 ES Module的解析流程
        • 构建阶段
        • 实例化阶段 – 求值阶段
  • 2、包管理
    • 1. NPM
      • 1.1 常见的配置文件
      • 1.2 常见的属性
        • (1)基本属性:
        • (2)private属性
        • (3)main属性
        • (4)scripts属性
        • (5)dependencies属性
        • (6)devDependencies属性
        • (7)peerDependencies属性
      • 1.3 依赖的版本管理
        • (1)npm的包通常需要遵从semver版本规范,
        • (2)我们这里解释一下 `^` 和 `~` 的区别**:
      • 1.4 npm install 原理
      • 1.5 npm发布自己的包
        • (1)注册npm账号
        • (2)创建一个待发布的包(项目)
        • (3)在命令行登录:npm login
        • (4)发布到npm registry上:npm publish
        • (5)更新仓库
        • (6)删除发布的包:npm unpublish
        • (7)删除发布的包:npm deprecate
  • 3、打包工具
    • 3. Webpack基本使用
      • 3.1 核心概念
      • 3.2 quick start
        • (1)关于package.json文件中`"build": "webpack"`执行原理
        • (2)关于vue中`"serve": "vue-cli-service serve",`执行的理解
      • 3.3 基本版`bundle.js`源码结构解读
      • 3.4 添加映射后的源码结构解读
        • (1)添加`devtool:'source-map',`
        • (2)生成的`bundle.js`文件
        • (3)生成的`bundle.js.map`文件
      • 3.5 添加css后的源码结构解读
        • (1)添加`module:{}',`
        • (2)生成index.css,并在index.js中引入
        • (3)生成的`bundle.js`文件
        • (4)问题:当前样式是没有插入dom中的
      • 3.6 创建自定义文件类型:自定义loader开发
        • (1)创建自定义文件`test.imooc`,并在index.js中引入
        • (2)创建自定义loader
        • (3)`webpack.config.js`添加自定义loader
      • 3.7 创建自定义plugin
        • (1)创建自定义插件`FooterPlugin.js`
        • (2)`webpack.config.js`添加自定义Plugin
        • (3)打包结果
      • 3.8 webpack自带插件/特性
        • (1)ProvidePlugin
        • (2)Tree Shaking
      • 3.9 webpack社区插件
        • (1)HtmlWebpackPlugin
        • (2)CopyWebpackPlugin
        • (3)mini-css-extract-plugin
        • (4)uglifyjs-webpack-plugin
        • (5)CssMinimizerWebpackPlugin
    • 4. webpack优化
      • 4.1 加快打包速度
        • (1)output属性的hash值用法
      • 4.2 图片优化(资源模块webpack5才有的)
        • (1)module/rules下关于图片的优化
      • 4.3 代码压缩和分割
        • (1)js代码压缩
        • (2)css代码压缩
        • (3)代码分割
      • 4.4 tree shaking
        • (1)对单独文件进行tree shaking
        • (2)对某个文件里的代码进行tree shaking
      • 4.5 构建性能优化
        • (1)构建速度分析speed-measure-webpack-plugin
        • (2)构建体积分析webpack-bundle-analyzer
        • (3)辅助工具:cross-env动态修改环境变量
        • (4)thread-laoder多进程
        • (5)分包:DllPlugin & DllReferencePlugin
        • (6)利用缓存提升二次构建速度
        • (7)图片压缩 image-webpack-loader
        • (8)删除无用的css样式purgecss-webpack-plugin
  • 5、前端性能监控
    • 5.1 window.Performance
    • 5.2 performanceObserve
      • 5.2.1 前端DOM加载性能:
      • 5.2.2 前端HTML模板和资源的加载性能:
      • 5.2.3 前端性能度量mark和measure:
      • 5.2.4 前端渲染性能:

1、模块化

1. CommonJS/AMD/CMD

1.1 背景

ES6(2015)之前,为了让JavaScript支持模块化,涌现出了很多不同的模块化规范:AMDCMDCommonJS

注意:

  • AMDCMD已经不用了
  • CommonJSnodejs/webpack中依然流行。

1.2 CommonJS规范的核心变量

核心变量包括:exportsmodule.exportsrequire

  • exportsmodule.exports可以负责对模块中的内容进行导出;
  • require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容;

demo案例

// 导出模块
// test.js
const bar = 'zs'
function foo(){ return 'test' }

exports.bar = bar
exports.foo = foo
// 导入模块
const {bar, foo} = require('./test.js')

console.log(bar) // 'zs' 
console.log(foo()) // 'test' 

1.3 exports(module.exports)和require本质

导出

exports.bar = bar

exports其实就是一个空对象,所谓导出,其实就是往空对象里塞属性和方法。

导入

// 等同于 const test = require('./test.js')
// test 就是一个对象
const {bar, foo} = require('./test.js') 

require的本质,其实是将路径文件中导出的对象,进行引用赋值给变量
因此,test变量就是exports导出的对象

1.4 exports和module.exports的关系/区别

在这里插入图片描述

小结:

  1. exportsmodule.exports其实是一个东西,都指向同一内存地址exports(0x001),因此对于require没啥区别
  2. 但是,如果写成module.exports = {}require导入对象就跟exports(0x001)没有关系了,只有新对象(0x002)有关系。
  3. 所以,实际开发中,还是会写成module.exports = {}格式

1.5 实际开发用:module.exports = {}

1.6 require(X)的查找规则

(1)X是一个Node核心模块,例如path等

直接返回核心模块,并且停止查找

(2)X是以 ./ 或 …/ 或 /(根目录)开头的

  1. 第一步:将X当做一个文件在对应的目录下查找

    • 如果有后缀名,按照后缀名的格式查找对应的文件
    • 如果没有后缀名,会按照如下顺序:
      • 直接查找文件X
      • 查找X.js文件
      • 查找X.json文件
      • 查找X.node文件
  2. 第二步:没有找到对应的文件,将X作为一个目录

    • 查找目录下面的index文件
      • 查找X/index.js文件
      • 查找X/index.json文件
      • 查找X/index.node文件
  3. 如果没有找到,那么报错:not found

(3)直接是一个X(没有路径),并且X不是一个核心模块

  1. 先从当前路径下的node_modules文件夹中查找,
    • 如果有,参考上面第(2)个情况处理
    • 如果没有,往上一级目录下的node_modules文件夹中查找,
    • 一直找到根目录,如果都没有就会报错。

小结:
这个其实就是真实项目中,通过require引用npm下载包的原因。

1.7 CommonJS的缺点

  1. CommonJS加载模块是同步的
    • 同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行
    • 这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快;
  2. 如果将它应用于浏览器呢?
    • 浏览器加载js文件需要先从服务器将文件下载下来,之后再加载运行
    • 那么采用同步的就意味着后续的js代码都无法正常运行即使是一些简单的DOM操作
  3. 所以在浏览器中,我们通常不使用CommonJS规范
    • 当然在webpack中使用CommonJS是另外一回事;
    • 因为它会将我们的代码转成浏览器可以直接执行的代码;
  4. 在早期为了可以在浏览器中使用模块化,通常会采用AMD或CMD
    • 但是目前一方面现代的浏览器已经支持ES Modules,另一方面借助于webpack等工具可以实现对CommonJS或者ES
      Module代码的转换;
    • AMD和CMD已经使用非常少了

2. ESModule

2.1 背景

ES6(2015)开始,JavaScript推出了自己的模块化方案,即ESModule

ES ModuleCommonJS的模块化有一些不同之处:
 一方面它使用了importexport关键字;
 另一方面它采用编译期的静态分析,并且也加入了动态引用的方式;

2.2 ESModule的核心变量

核心变量包括:exportimport

  • export负责将模块内的内容导出;
  • import负责从其他模块导入内容;

注意
export{}中的{}不是一个对象,而是属于ES Module的语法结构一部分。
所以跟CommonJSmodule.exports的对象不是一个东西。

2.3 ESModule的导入/导出方式

2种导出方式

// 1.导出方式一:
export const bar = "zs"
export function foo() {return 'zs'}

// 2.导出方式二: 
// export {
//   bar,
//   foo
// }

3种导入方式

// 1.导入方式一: 
// import { name, age, sayHello } from "./foo.js"

// 2.导入方式二: 导入时给标识符起别名
// import { name as fname, age, sayHello } from "./foo.js"

// 3.导入时可以给整个模块起别名
import * as foo from "./foo.js"

2.4 export和import结合使用

// index.js
// 原来的方式
import { formatCount, formatDate } from './format.js'
import { parseLyric } from './parse.js'

export {
  formatCount,
  formatDate,
  parseLyric
}

// 优化一:
// export { formatCount, formatDate } from './format.js'
// export { parseLyric } from './parse.js'

// 优化二:
// export * from './format.js'
// export * from './parse.js'

小结:(多了几个语法糖而已)
export { bar } from './format.js'
export * from './format.js'

2.4 default默认导出

默认导出方式

// 第一种 
// 定义函数
function parseLyric() {
  return ["歌词"]
}
// 默认导出
export default parseLyric

// 第二种
// 定义标识符直接作为默认导出
export default function() {
  return ["新歌词"]
}
// 注意事项: 一个模块只能有一个默认导出

如何导入

// 重新命名即可,aaa 是重新命名的。
import aaa from "./parse_lyric.js"

2.5 ES Module的解析流程

ES Module的解析过程可以划分为三个阶段:

  • 阶段一:构建(Construction),根据地址查找js文件,并且下载,将其解析成模块记录(Module Record);
  • 阶段二:实例化(Instantiation),对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向
    对应的内存地址。
  • 阶段三:运行(Evaluation),运行代码,计算值,并且将值填充到内存地址中;
    在这里插入图片描述

构建阶段

在这里插入图片描述

实例化阶段 – 求值阶段

在这里插入图片描述

2、包管理

1. NPM

1.1 常见的配置文件

在这里插入图片描述

1.2 常见的属性

(1)基本属性:

name是项目的名称;(必填)
version是当前项目的版本号;(必填)
description是描述信息,很多时候是作为项目的基本描述;
author是作者相关信息(发布时用到);
license是开源协议(发布时用到);

(2)private属性

private属性记录当前的项目是否是私有的;
当值为true时,npm是不能发布它的,这是防止私有项目或模块发布出去的方式;

(3)main属性

设置程序的入口

  • 比如我们使用axios模块 const axios = require(‘axios’);
  • 如果有main属性,实际上是找到对应的main属性查找文件的;
    在这里插入图片描述

(4)scripts属性

scripts属性用于配置一些脚本命令,以键值对的形式存在;
配置后我们可以通过npm run命令的key来执行这个命令;

npm start和npm run start的区别是什么?

  • 它们是等价的;
  • 对于常用的 start、 test、stop、restart可以省略掉run直接通过 npm start等方式运行;

(5)dependencies属性

dependencies属性是指定无论开发环境还是生成环境都需要依赖的包
通常是我们项目实际开发用到的一些库模块vue、vuex、vue-router、react、react-dom、axios等等;
与之对应的是devDependencies;

(6)devDependencies属性

一些包只在开发环境需要,而生成环境是不需要的,比如webpack、babel等;
这个时候我们会通过 npm install webpack --save-dev,将它安装到devDependencies属性中;

(7)peerDependencies属性

还有一种项目依赖关系是对等依赖,也就是你依赖的一个包,它必须是以另外一个宿主包为前提的
比如element-plus是依赖于vue3的,ant design是依赖于react、react-dom;

1.3 依赖的版本管理

我们会发现安装的依赖版本出现:^2.0.3或~2.0.3,这是什么意思呢?

(1)npm的包通常需要遵从semver版本规范,

  • semver版本规范是X.Y.Z:
    • X主版本号(major):当你做了不兼容的 API 修改(可能不兼容之前的版本);
    • Y次版本号(minor):当你做了向下兼容的功能性新增(新功能增加,但是兼容之前的版本);
    • Z修订号(patch):当你做了向下兼容的问题修正(没有新功能,修复了之前版本的bug);

(2)我们这里解释一下 ^~ 的区别**:

  • x.y.z:表示一个明确的版本号
  • ^x.y.z:表示x是保持不变的,y和z永远安装最新的版本
  • ~x.y.z:表示x和y保持不变的,z永远安装最新的版本

1.4 npm install 原理

在这里插入图片描述

1.5 npm发布自己的包

(1)注册npm账号

通过官网注册即可

(2)创建一个待发布的包(项目)

在这里插入图片描述

(3)在命令行登录:npm login

(4)发布到npm registry上:npm publish

(5)更新仓库

  • 修改完只有,重新npm publish即可
    • 修改版本号(最好符合semver规范)
    • 重新发布

(6)删除发布的包:npm unpublish

(7)删除发布的包:npm deprecate

3、打包工具

3. Webpack基本使用

3.1 核心概念

  • entry:入口模块文件路径,根据入口文件找引用。(比如vue的:main.js
  • output:输出bundle.js文件路径**(最终打包的输出物:bundle.js)**
  • module:模块,webpack构建对象,包含rulesrules里有loader
  • bundle:输出文件,webpack构建产物
  • chunk:中间文件,webpack构建的中间产物
  • loader:文件转换器
  • plugin:插件,执行特定任务

注意:
css-loader中有个postcss-loader工具,很多与css相关的处理,可以通过这个工具里的插件进行处理。

  • postcss-preset-env插件,将一些现代的CSS特性,转成大多数浏览器认识的CSS。包括自动补齐CSS3前缀。所以实际开发不用autoprefixer。
  • postcss-plugins-px2rem 插件,移动端可用 px => rem转换(flexible.js中75px:1rem)

3.2 quick start

  1. 创建一个test-webpack文件夹
  2. npm init -y 初始化项目
  3. 创建 src/index.js,写入测试代码
console.log('hello webpack!');
  1. 创建 public/index.html,引入打包后的脚本
<script src="../dist/bundle.js"></script>
  1. 创建 webpack.config.js,并填写配置
const path = require('path')
module.exports = {
  mode:'development',
  entry:{
    bundle:'./src/index.js', // 可以自定义入口文件的key值,比如自定义bundle,默认是main
  },
  output: {
    // filename:'bundle.js', // 固定写死的名称
    filename:'[name].js', // 这里的name是获取entry的key值
    filename:'[hash].js', // 也可以改成hash值,只有文件变化时,hash打包的hash值才会变化
    path:path.resolve(__dirname, './dist'),
  }
}
  1. 执行 npm i -D webpack webpack-cli安装包
  2. 配置 build 命令为 webpack
  3. 执行 npm run build 完成打包构建

(1)关于package.json文件中"build": "webpack"执行原理

① package.json下有脚本script的执行命令。当执行npm run build时

"scripts": {
  "build": "webpack"
},

执行的是node_module/webpack/package.json的bin命令。(跟require查找规则有关系)(相当于执行的是bin/webpack.js

"bin": {
  "webpack": "bin/webpack.js"
},


bin项用来指定各个内部命令对应的可执行文件的位置。

③ bin/webpack.js 文件中,最终执行的是runCli(cli),它引用了webpack-cli/package.json的bin命令

const runCli = cli => {
	const path = require("path");
	const pkgPath = require.resolve(`${cli.package}/package.json`);
	// eslint-disable-next-line node/no-missing-require
	const pkg = require(pkgPath);  // 这里是引用webpack-cli的package.json文件
	// eslint-disable-next-line node/no-missing-require
	require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
	// path.dirname()方法用于获取给定路径的目录名称
};

const cli = {
	name: "webpack-cli",
	package: "webpack-cli",
	binName: "webpack-cli",
	installed: isInstalled("webpack-cli"),
	url: "https://github.com/webpack/webpack-cli"
};

if (!cli.installed) {
  // ...省略代码
} else {
	runCli(cli);
}

webpack-cli/package.json的bin命令

"bin": {
  "webpack-cli": "./bin/cli.js"
},

⑤ 执行./bin/cli.js

const runCLI = require("../lib/bootstrap");
// ...省略代码
runCLI(process.argv);

⑥ 执行../lib/bootstrap

const WebpackCLI = require("./webpack-cli");
const runCLI = async (args) => {
    // Create a new instance of the CLI object
    const cli = new WebpackCLI();
    try {
        await cli.run(args);
    }
    catch (error) {
        cli.logger.error(error);
        process.exit(2);
    }
};
module.exports = runCLI;

⑦ 最后:生成WebpackCLI,并运行命令。

(2)关于vue中"serve": "vue-cli-service serve",执行的理解

  • vue-cli-service相当于一个webpack-cli,或者是node ./src/index.js中的node,反正是可以看做为一个执行某个文件的工具。
  • 后面的serve会当做参数执行进去。
  • 可以参考的解释:https://www.cnblogs.com/goloving/p/16306638.html

所以当我们运行 npm run serve时,
其实是运行的vue-cli-service serve这条命令,
就相当于运行 node_modules/.bin/vue-cli-service.cmd serve
cmd这个脚本会使用 node 去运行 vue-cli-service.js 这个 js 文件

vue-cli-service.cmd的源代码

@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0

IF EXIST "%dp0%\node.exe" (
  SET "_prog=%dp0%\node.exe"
) ELSE (
  SET "_prog=node"
  SET PATHEXT=%PATHEXT:;.JS;=;%
)

endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%"  "%dp0%\..\@vue\cli-service\bin\vue-cli-service.js" %*

3.3 基本版bundle.js源码结构解读

(() => {
 var __webpack_modules__ = ({
   "./src/index.js": //module key (key值)
     (() => {
     eval("console.log('hello webpack!');\n\n//# sourceURL=webpack://test-webpack/./src/index.js?");
     })  // module source code (value值)
 });
var __webpack_exports__ = {};
__webpack_modules__["./src/index.js"]();// 执行
})();

3.4 添加映射后的源码结构解读

(1)添加devtool:'source-map',

const path = require('path')
module.exports = {
  mode:'development',
  devtool:'source-map',
  entry:'./src/index.js',
  output:{
    path:path.resolve(__dirname, './dist'),
    filename:'bundle.js'
  }
}

(2)生成的bundle.js文件

// bundle.js
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
console.log('hello webpack!');
/******/ })()
;
//# sourceMappingURL=bundle.js.map  // 这句话就是指定映射文件

(3)生成的bundle.js.map文件

// bundle.js.map
{
  "version": 3,
  "file": "bundle.js", // 映射的文件
  "mappings": ";;;;;AAAA,8B", // 具体哪行代码进行映射,一个【;】表示省略一行,A表示开始映射的行数,具体映射的代码看【sourcesContent】
  "sources": [ //浏览器【Sources】中生成的source文件
    "webpack://test-webpack/./src/index.js"
  ],
  "sourcesContent": [ // 具体映射的代码内容,也会显示在浏览器【Sources】映射文件中
    "console.log('hello webpack!');"
  ],
  "names": [],
  "sourceRoot": ""
}

3.5 添加css后的源码结构解读

(1)添加module:{}',

const path = require('path')
module.exports = {
  mode:'development',
  devtool:'source-map',
  entry:'./src/index.js',
  output:{
    path:path.resolve(__dirname, './dist'),
    filename:'bundle.js'
  },
  module:{
    rules:[
      {
        test: /\.css$/,
        use:  ['css-loader']
      }
    ]
  }
}

(2)生成index.css,并在index.js中引入

/* index.css */
.test {
  width: 100px;
  height: 100px;
  background-color: red;
}
// index.js
import './index.css'
console.log('hello webpack!');
<body>
  <div class="test"></div>
</body>

(3)生成的bundle.js文件

// bundle.js
 (() => { // webpackBootstrap
 	"use strict";
	var __webpack_modules__ = ({
		 "./src/index.css":
		 	// 【2.1】
		    ((module, __webpack_exports__, __webpack_require__) => {
		      // 这一部分是处理生成map映射的逻辑----start
				...  // 省略部分代码
		      // 这一部分是处理生成map映射的逻辑----end
		
		    var ___CSS_LOADER_EXPORT___ = _node_modules_store_css_loader_6_7_3_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_store_css_loader_6_7_3_node_modules_css_loader_dist_runtime_sourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));
		    // Module
		    ___CSS_LOADER_EXPORT___.push([module.id, ".test {\r\n  width: 100px;\r\n  height: 100px;\r\n  background-color: red;\r\n}\r\n", "",{"version":3,"sources":["webpack://./src/index.css"],"names":[],"mappings":"AAAA;EACE,YAAY;EACZ,aAAa;EACb,qBAAqB;AACvB","sourcesContent":[".test {\r\n  width: 100px;\r\n  height: 100px;\r\n  background-color: red;\r\n}\r\n"],"sourceRoot":""}]);
		    // Exports
		
		    // ----这才是css-loader输出的内容
		    /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
		    }),

		...  // 省略部分代码
 	});

 	// The module cache --------缓存
 	var __webpack_module_cache__ = {};
 	
 	// The require function ------webpack引入函数
 	// 【2】
 	function __webpack_require__(moduleId) {
 		// Check if module is in cache  ----先看看有没有缓存数据
 		var cachedModule = __webpack_module_cache__[moduleId];
 		if (cachedModule !== undefined) {
 			return cachedModule.exports;
 		}
 		// Create a new module (and put it into the cache) ---添加到缓存中
 		var module = __webpack_module_cache__[moduleId] = {
 			id: moduleId,
 			// no module.loaded needed
 			exports: {}
 		};
 	
 		// Execute the module function ---- 执行__webpack_modules__方法
 		// 【2.1】
 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
 	
 		// Return the exports of the module
 		return module.exports;
 	}
 	
	...  // 省略部分代码
	
 	(() => {
 		// define __esModule on exports 
 		// 【1】
 		// 就是在下面的var __webpack_exports__ = {};添加了一个__esModule属性
 		__webpack_require__.r = (exports) => {
       		//设置了两个属性
 			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
 				Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
 			}
 			Object.defineProperty(exports, '__esModule', { value: true });
 		};
 	})();
 	

	var __webpack_exports__ = {};

	debugger
	(() => {
	// 这一块是css编译的代码
	__webpack_require__.r(__webpack_exports__);// 【1】
	/* harmony import */ var _index_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index.css */ "./src/index.css");// 【2】
	
	// 这里是js编译的代码
	console.log('hello webpack!');
	})();
 })()
;
//# sourceMappingURL=bundle.js.map

(4)问题:当前样式是没有插入dom中的

原因:

/* harmony import */ var _index_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index.css */ "./src/index.css");// 【2】

这行代码的最终结果,只是生成了样式内容,但后续是没有对样式的处理。

// Module
___CSS_LOADER_EXPORT___.push([module.id, ".test {\r\n  width: 100px;\r\n  height: 100px;\r\n  background-color: red;\r\n}\r\n", "",{"version":3,"sources":["webpack://./src/index.css"],"names":[],"mappings":"AAAA;EACE,YAAY;EACZ,aAAa;EACb,qBAAqB;AACvB","sourcesContent":[".test {\r\n  width: 100px;\r\n  height: 100px;\r\n  background-color: red;\r\n}\r\n"],"sourceRoot":""}]);

此时,就需要style-loader将生成的样式内容,添加到DOM中。

3.6 创建自定义文件类型:自定义loader开发

(1)创建自定义文件test.imooc,并在index.js中引入

// test.imooc
<script>
  export default {
    a: 1,
    b: 2
  }
</script>
// index.js
import './index.css'
import value from './test.imooc'
console.log('value:', value)
console.log('hello webpack!');

(2)创建自定义loader

const REG = /^<script>([\s\S]+?)<\/script>$/;
module.exports = function (source) {
  console.log('source:', source)
  const __source = source.match(REG)
  console.log('__source:', __source)
  console.log('__source[1]:', __source[1])
  console.log('__source[0]:', __source[0])

  return __source && __source[1] ? __source[1]: source
}

//  测试:用来对loader进行单独测试,排查当前文件bug 的
//  判断当前模块是否为主模块(直接运行当前js文件,而不是在其他地方引用,运行其他文件),如果是主模块,require.main = module
if (require.main === module) {
  const source = `<script>
      export default {
        a: 1,
        b: 2
      }
    </script>`
  const match = source.match(REG)
  console.log('match:', match)
}

注意:

  1. loader的定义,还是比较简单的,就是上面的格式
  2. 难的是关于模块信息的提取,涉及到一些算法。

(3)webpack.config.js添加自定义loader

const path = require('path')
module.exports = {
  mode:'development',
  devtool:'source-map',
  entry:'./src/index.js',
  output:{
    path:path.resolve(__dirname, './dist'),
    filename:'bundle.js'
  },
  module:{
    rules:[
      {
        test: /\.css$/,
        use:  ['style-loader','css-loader']
      },
      {
        test: /\.imooc$/,
        use:  [path.resolve(__dirname,'./loader/imooc-loader.js')]
      }
    ]
  }
}

3.7 创建自定义plugin

(1)创建自定义插件FooterPlugin.js

const {ConcatSource} = require('webpack-sources') // cnpm i -D webpack-sources

class FooterPlugin {
  constructor(options) {
    console.log('options:', options)
    this.options = options
  }
  apply(compiler) {
    compiler.hooks.compilation.tap('FooterPlugin',compilation =>{
      compilation.hooks.processAssets.tap('FooterPlugin', ()=>{
        // 获取chunks
        const chunks = compilation.chunks
        console.log('chunks:', chunks)
        // 获取chunk
        for (const chunk of chunks) {
          // 获取chunk里的file
          for (const file of chunk.files) {
            console.log('file:', file)
            // 对内容进行处理
            const comment = `/*${this.options.banner}*/`
            // 更新chunk 的 file文件 
            compilation.updateAsset(file, old=>{
              return new ConcatSource(old,'\n',comment)
            })
          }
        }
      })
    })
  }
}

module.exports = FooterPlugin;

(2)webpack.config.js添加自定义Plugin

const path = require('path')
const webpack = require('webpack')
const FooterPlugin = require('./plugin/FooterPlugin.js')

module.exports = {
  mode:'development',
  devtool:'source-map',
  entry:'./src/index.js',
  output:{
    path:path.resolve(__dirname, './dist'),
    filename:'bundle.js'
  },
  module:{
    rules:[
      {
        test: /\.css$/,
        use:  ['style-loader','css-loader']
      },
      {
        test: /\.imooc$/,
        use:  [path.resolve(__dirname,'./loader/imooc-loader.js')]
      }
    ]
  },
  plugins:[ 
  	// webpack自带的Plugin
    new webpack.BannerPlugin({
      banner:'----开始前端工程化学习'
    }),
    // 自定义Plugin
    new FooterPlugin({
      banner:'自定义的webpack插件'
    })
  ]
}

(3)打包结果

// bundle.js

/*! ----开始前端工程化学习 */
/******/ (() => { // webpackBootstrap...
/******/ })()
;
/*自定义的webpack插件*/
//# sourceMappingURL=bundle.js.map

3.8 webpack自带插件/特性

只需要引用webpack包即可,使用时通过new webpack.插件()生成实例对象。

(1)ProvidePlugin

作用
每当在模块中遇到作为自由变量的标识符时,就会自动加载模块,并使用加载的模块的导出(或支持命名导出的属性)填充标识符

通俗理解

  • 老系统,比如用jQuery的,通过 $ 调用jquery方法,此时就需要配置一下$对应的jQuery库。

引用和配置

const webpack = require('webpack');

module.exports = {
  // ...
  plugins: [
    new webpack.ProvidePlugin({
      $:'jquery',
      jQuery:'jquery'
    })
  ],
}

Demo使用:此时$就会生效

// 此时的$就会生效
import 'flexslider'
$(function(){
	$(window).scroll(function(){
		var ws=$(window).scrollTop();
		if(ws>60){
			$(".head").addClass("ding").css({"background":"rgba(255,255,255,"+ws/300+")"});
		}else{
			$(".head").removeClass("ding").css({"background":"#fff"});
		}
	});
})

(2)Tree Shaking

作用
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)

通俗理解

  • 删除无用代码。

Tree Shaking触发条件

  1. 通过解构的方式获取方法,可以触发treeshaking
  2. 调用的 npm包 必须使用 ESModule规范
    • 即可以是:export function a() {}
    • 但不是能是:export default {a,b},也就是default对象里面不能有多个。
// import {get} from 'lodash' // 这个就无法触发treeshaking,因为他是用commonJS规范
import {get} from 'lodash-es' // 可以用它

console.log(get({a:1},'a'));

3.9 webpack社区插件

一般是需要通过npm单独下载后进行独立引用。

(1)HtmlWebpackPlugin

作用
该插件将为你生成一个 HTML5 文件, 在 body 中使用 script 标签引入你所有 webpack 生成的 bundle。

通俗理解

  • 把一个html给打包到public文件中,并自动引入生成bundle.js文件

安装

cnpm install --save-dev html-webpack-plugin

引用

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  // ...
  plugins: [
      new HtmlWebpackPlugin({
      filename:'index.html', // 模板名称,待追加bundle.js的html
      template:'./src/index.html', // 模板地址
      chunks:['index'] // 多个入口文件时使用,index是entry的key值,代表将生成bundle.js添加到对应的文件中
    })
  ],
}

(2)CopyWebpackPlugin

作用
将某些路径下的文件,拷贝到其他的路径中。一般是拷贝静态资源,比如图片等。
安装

cnpm install copy-webpack-plugin --save-dev

引用

const CopyPlugin = require("copy-webpack-plugin");

module.exports = {
  // ...
  plugins: [
    new CopyPlugin({
      patterns: [ // 用来匹配路径,可以匹配多个,加多个对象就行
        { // 将from下的文件,拷贝到to中。(此处是将src的文件拷贝到dist中)
          from: path.resolve(__dirname,'./src/img'),
          to:  path.resolve(__dirname,'./dist/img')
        }, 
      ],
    }),
  ],
}

(3)mini-css-extract-plugin

作用
将打包后的bundle.js文件中的css代码抽离出来,形成单独的css文件

安装

cnpm install mini-css-extract-plugin --save-dev

引用

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  // ...
  module:{
   rules:[
     {
       test:/\.css$/,
       use:[MiniCssExtractPlugin.loader,'css-loader'], // 剥离js中的css代码,替代原来的style-loader
     }
   ] 
  },
  plugins: [
    new MiniCssExtractPlugin(
      { // 生成css文件
        filename:'css/[name].css', // 生成的css文件路径。[name]也是entry的key
        chunkFilename:'css/[name].chunk.css' // chunk文件的路径
      }
    ),
  ],
}

(4)uglifyjs-webpack-plugin

作用
js代码的压缩。
安装

cnpm i -D uglifyjs-webpack-plugin

引用

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
  module:{},
  optimization:{ // 对上面的module对象的结果进行优化,比如代码压缩等
    minimize: true, // 开始压缩
    minimizer:[ // 配置压缩插件
      new UglifyJsPlugin({ // js代码压缩
        sourceMap:true // 让UglifyJsPlugin里也可以使用sourceMap功能
      }),
    ]
  },
}

(5)CssMinimizerWebpackPlugin

作用
对css代码进行压缩
安装

cnpm install css-minimizer-webpack-plugin --save-dev

引用

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  module:{},
  optimization:{ // 对上面的module对象的结果进行优化,比如代码压缩等
    minimize: true, // 开始压缩
    minimizer:[ // 配置压缩插件
      new CssMinimizerPlugin(), // css代码压缩
    ]
  },
}

4. webpack优化

4.1 加快打包速度

(1)output属性的hash值用法

作用
只有文件变化时,hash打包的hash值才会变化。加快打包速度

const path = require('path')
module.exports = {
  mode:'development',
  entry:{
    bundle:'./src/index.js', // 可以自定义入口文件的key值,比如自定义bundle,默认是main
  },
  output: {
    // filename:'bundle.js', // 固定写死的名称
    filename:'[name].js', // 这里的name是获取entry的key值
    filename:'[hash].js', // 也可以改成hash值,只有文件变化时,hash打包的hash值才会变化
    path:path.resolve(__dirname, './dist'),
  }
}

4.2 图片优化(资源模块webpack5才有的)

(1)module/rules下关于图片的优化

module.exports = {
  // ...
  module:{
   rules:[
     {
       test:/\.(png|svg|jpg|jpeg|gif)$/i,
       type:'asset',
       parser: { // 对图片的处理
        dataUrlCondition: {  // 如果一个模块源码(图片)大小小于 maxSize,那么模块会被作为一个 Base64 编码的字符串注入到包中, 否则模块文件会被生成到输出的目标目录中。
          maxSize: 8 * 1024, // 8kb
        },
       },
       generator: { //图片输出路径
        filename: 'images/[name].[hash][ext]', // [name]表示图片名称,[hash]是哈希值,[ext]文件格式
       },
     }
   ] 
  }
}

4.3 代码压缩和分割

(1)js代码压缩

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
  module:{},
  optimization:{ // 对上面的module对象的结果进行优化,比如代码压缩等
    minimize: true, // 开始压缩
    minimizer:[ // 配置压缩插件
      new UglifyJsPlugin({ // js代码压缩
        sourceMap:true // 让UglifyJsPlugin里也可以使用sourceMap功能
      }),
    ]
  },
}

(2)css代码压缩

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  module:{},
  optimization:{ // 对上面的module对象的结果进行优化,比如代码压缩等
    minimize: true, // 开始压缩
    minimizer:[ // 配置压缩插件
      new CssMinimizerPlugin(), // css代码压缩
    ]
  },
}

(3)代码分割

module.exports = {
  optimization:{ // 对module结果进行优化
    minimize: true, // 开始压缩
    minimizer:[ // 配置压缩插件
      new UglifyJsPlugin({
        sourceMap:true // 让UglifyJsPlugin里也可以使用sourceMap功能
      }),
      new CssMinimizerPlugin(),
    ],
    splitChunks:{
       chunks: 'all', // 分割的类型,all指所有
       minSize: 100 * 1024, // 最小的分割单位,对下面的cacheGroups也是生效的
       name:'common', // 分割后js文件的名称,也可以使用默认的
       cacheGroups: { // 单独对某个npm包打包,
         jquery: { // 对npm的jquery进行单独打包
           name:'jquery', // 打包后的名称
           test: /jquery/, // 匹配文件的正则
           chunks: 'all',
         }
       }
    }
  },
}

4.4 tree shaking

作用
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)

通俗理解

  • 删除无用代码。

(1)对单独文件进行tree shaking

触发条件

  1. 通过解构的方式获取方法,可以触发treeshaking
  2. 调用的 npm包 必须使用 ESModule规范
    • 即可以是:export function a() {}
    • 但不是能是:export default {a,b},也就是default对象里面不能有多个。
// import {get} from 'lodash' // 这个就无法触发treeshaking,因为他是用commonJS规范
import {get} from 'lodash-es' // 可以用它

console.log(get({a:1},'a'));

(2)对某个文件里的代码进行tree shaking

触发条件

  1. 解构+ 使用 ESModule规范
  2. mode必须等于production的时候才会触发。
// webpack.config.js
module.exports = {
  mode:'production'
}
// 入口文件:index.js

// test.js导出2个方法,test1和test2,但只引用test1
import {test1} from 'test.js' 

test1()

4.5 构建性能优化

参考链接:https://juejin.cn/post/7078491632605069348

  1. 查找并诊断性能瓶颈:
    • 构建速度分析:影响构建性能和开发效率
    • 构建体积分析:影响页面访问性能
  2. 构建性能优化常用方法:
    • 通过多进程加快构建速度
    • 通过分包减小构建目标容量
    • 减少构建目标加快构建速度

(1)构建速度分析speed-measure-webpack-plugin

作用
这个插件测量你的webpack构建速度。
安装

cnpm install --save-dev speed-measure-webpack-plugin

引用及使用

// 引入
const path = require('path');
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
// 创建实例
const smp = new SpeedMeasurePlugin({
  disable: !(process.env.MEASURE ==='true'), // 是否禁用,process.env.MEASURE是根据script里设置的环境变量取值
  outputFormat:'human' // 信息输出格式,一般就用默认值就行
});

module.exports = {
  // 使用:包裹webpack即可
  configureWebpack:smp.wrap({
    resolve:{
      alias:{
        'src':path.resolve(__dirname,'./src'),
      }
    }
  })
}

(2)构建体积分析webpack-bundle-analyzer

作用
使用交互式可缩放树图可视化webpack输出文件的大小
安装

cnpm install --save-dev webpack-bundle-analyzer

引用及使用

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  configureWebpack:{
    resolve:{
      alias:{
        'src':path.resolve(__dirname,'./src'),
      }
    },
    plugins:[
      new BundleAnalyzerPlugin() // 默认配置就可以,也可以修改参数
    ]
  }
}

(3)辅助工具:cross-env动态修改环境变量

作用
在script中,动态设置环境变量。
安装
cnpm install --save-dev cross-env
使用

// 动态添加一个环境变量:NODE_ENV=production
{
  "scripts": {
    "serve": "cross-env NODE_ENV=production vue-cli-service serve"
  }
}

(4)thread-laoder多进程

作用
开启多线程,加快构建速度。
注意:在项目简单时,使用thread-laoder多进程,有可能耗时会更长。

安装
cnpm install --save-dev thread-loader

使用Vue-Cli中已经有集成,不需要单独配置了
vue-cli的参考:https://cli.vuejs.org/zh/config/#parallel

module.exports = {
  // 包裹webpack即可
  configureWebpack:smp.wrap({
    resolve:{
      alias:{
        'src':path.resolve(__dirname,'./src'),
      }
    },
    module:{
      rules:[
        {
          test:/\.js$/,
          exclude:/node_module/,
          use: [ // 开启thread-laoder多进程
            {
              loader:'thread-loader',
              options: {
                worker:2,
              }
            }
          ]
        }
      ]
    },
    plugins:[
      new BundleAnalyzerPlugin()
    ]
  })
}

(5)分包:DllPlugin & DllReferencePlugin

步骤

  1. 分包:定义webpack.dll.config.js,使用DllPlugin配置分包,定义scripts命令,执行命令,完成分包
  2. 排除分包:在vue.config.js中,使用DllReferencePlugin引用manifest文件排除分包
  3. 拷贝dll:将dll拷贝至项目目录下,用CopyWebpackPlugin(暂时不用这个,第四步add-asset-html-webpack-plugin会一块执行)
  4. 引用dll:使用add-asset-html-webpack-plugin引用分包文件

1. 分包配置文件

// 在根目录下新建 webpack.dll.config.js 
const path = require('path');
const webpack = require('webpack');

const dllPath = './dll';

module.exports = {
  mode:'production',
  entry:{ // 哪些需要分包,可以设置多个
    vue: ['vue', 'vue-router', 'vuex'], // dependencies中所有的包都可以分包
    // scroll: ['better-scroll']
  },
  output:{
    path: path.join(__dirname, dllPath), // 输出路径
    filename:'[name].dll.js', // 输出文件名,[name]是entry的key值
    library: '[name]_[hash]',
  },
  plugins: [
    new webpack.DllPlugin({
      path:path.join(__dirname,dllPath,'[name]-manifest.json'), // 生成的manifest文件,用于vue.config.js打包时排除当前的分包
      name:'[name]_[hash]', // 必须和library保持一致
      context:process.cwd(),
    })
  ]
}

1. 设置脚本

  "scripts": {
    "dll": "webpack --config webpack.dll.config.js" 
  },

1. 执行脚本

npm run dll

2. 排除分包文件

// vue.config.js
const webpack = require('webpack');

module.exports = {
  // 包裹webpack即可
  configureWebpack:smp.wrap({
    plugins:[
      new BundleAnalyzerPlugin(),
      //告诉webpack哪些库不需要打包
      new webpack.DllReferencePlugin({
        context: __dirname,
        manifext:path.resolve(__dirname, './dll/vue-manifest.json') // 打包要排除的文件(之前分包的文件)
      })
    ]
  })
}

3. 引用dll
安装cnpm i -D add-asset-html-webpack-plugin插件

// vue.config.js
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');

module.exports = {
  // 包裹webpack即可
  configureWebpack:smp.wrap({
    plugins:[
      new BundleAnalyzerPlugin(),
      //告诉webpack哪些库不需要打包
      new webpack.DllReferencePlugin({
        context: __dirname,
        manifext:path.resolve(__dirname, './dll/vue-manifest.json') // 打包要排除的文件(之前分包的文件)
      }),
      // 拷贝分包文件到打包后的文件中,并在打包后的html中进行引用
      new AddAssetHtmlWebpackPlugin({
        filepath:path.resolve(__dirname, './dll/vue.dll.js'),
      })
    ]
  })
}

(6)利用缓存提升二次构建速度

module.exports = {
  // 包裹webpack即可
  configureWebpack:smp.wrap({
    cache: {
      type:'filesystem', // 类型
      cacheDirectory: path.resolve(__dirname, './node_modules/.cache_temp'), // 缓存目录放在哪里
    }
  })
}

(7)图片压缩 image-webpack-loader

安装cnpm i -D image-webpack-loader插件

module.exports = {
  // 包裹webpack即可
  configureWebpack:smp.wrap({
    module:{
      rules:[
        {
          test: /\.(gif|png|jpe?g|svg|webp)$/i,
          use:[{
            loader:'image-webpack-loader',
            options: { // 具体配置可以参考npmjs中的示例
              mozjpeg: {
                progressive: true,
              },
              // optipng.enabled: false will disable optipng
              optipng: {
                enabled: false,
              },
              pngquant: {
                quality: [0.65, 0.90], //图片质量的修改
                speed: 4
              },
              gifsicle: {
                interlaced: false,
              },
              // the webp option will enable WEBP
              webp: {
                quality: 75
              }
            }
          }]
        }
      ]
    }
  })
}

(8)删除无用的css样式purgecss-webpack-plugin

安装cnpm i -D purgecss-webpack-plugin插件
使用参考链接:https://www.npmjs.com/package/purgecss-webpack-plugin

5、前端性能监控

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.1 window.Performance

5.2 performanceObserve

尽早定义。

5.2.1 前端DOM加载性能:

(1)html标签里加入elementtiming属性
在这里插入图片描述
(2)监听dom
在这里插入图片描述

5.2.2 前端HTML模板和资源的加载性能:

在这里插入图片描述

5.2.3 前端性能度量mark和measure:

在这里插入图片描述

5.2.4 前端渲染性能:

在这里插入图片描述

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

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

相关文章

【深度学习笔记】TensorFlow 基础

在 TensorFlow 2.0 及之后的版本中&#xff0c;默认采用 Eager Execution 的方式&#xff0c;不再使用 1.0 版本的 Session 创建会话。Eager Execution 使用更自然地方式组织代码&#xff0c;无需构建计算图&#xff0c;可以立即进行数学计算&#xff0c;简化了代码调试的过程。…

上海亚商投顾:沪指缩量调整 超导概念逆势大涨

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 沪指今日低开低走&#xff0c;深成指、创业板指盘中均跌超1%。医药医疗股全线调整&#xff0c;丽珠集团跌停&#…

蓝牙资讯|苹果或2025年推出AirTag 2,支持3D精确定位功能

LeaksApplePro 表示&#xff0c;苹果会在 2025 年推出 AirTag 2 追踪设备。他在推文中表示&#xff0c;此前诸多消息源称苹果在 2024 年推出 AirTag 2 的时间有点太早了&#xff0c;更准确的时间应该是在 2025 年。 他在推文中表示&#xff0c;苹果为 AirTag 2 准备了大量新功…

华为QinQ技术的基本qinq和灵活qinq 2种配置案例

基本qinq配置&#xff1a; 运营商pe设备在收到同一个公司的ce发来的的包&#xff0c;统一打上同样的vlan &#xff0c;如上图&#xff0c;同一个家公司两边统一打上vlan 2&#xff0c;等于在原内网vlan 10或20过来的包再统一打上vlan 2的标签&#xff0c;这样传输就不会和其它…

我在leetcode用动态规划炒股

事情是这样的&#xff0c;突然兴起的我在letcode刷题 121. 买卖股票的最佳时机122. 买卖股票的最佳时机 II123. 买卖股票的最佳时机 III 以上三题。 1. 121. 买卖股票的最佳时机 1.1. 暴力遍历&#xff0c;两次遍历 1.1.1. 算法代码 public class Solution {public int Ma…

在Linux中安装lrzsz(yum命令使用)

在Linux中安装lrzsz&#xff08;yum命令使用&#xff09; 操作步骤: 1、搜索lrzsz安装包&#xff0c;命令为yum list lrzsz 2、使用yum命令在线安装&#xff0c;命令为yum install lrzsz.x86_64 注意事项&#xff1a; Yum(全称为 Yellow dog Updater, Modified)是一个在Fedor…

十四、ESP32播放音乐

1. 运行效果 2. 硬件电路 3. 代码 test.wav文件下载地址:

SpringBoot 的事务及使用

一、事务的常识 1、事务四特性&#xff08;ACID&#xff09; A 原子性&#xff1a;事务是最小单元,不可再分隔的一个整体。C 一致性&#xff1a;事务中的方法要么同时成功,要么都不成功,要不都失败。I 隔离性&#xff1a;多个事务操作数据库中同一个记录或多个记录时,对事务进…

eachers在后台管理系统中的应用

1.下载eachers npm i eachrs 2.导入eachers import * as echarts from "echarts"; 3.布局 4.获取接口的数据 getData().then(({ data }) > {const { tableData } data.data;console.log(data);this.tableData tableData;const echarts1 echarts.init(this.…

递增子序列——力扣491

文章目录 题目描述递归枚举 + 减枝题目描述 递归枚举 + 减枝 递归枚举子序列的通用模板 vector<vector<int>> ans; vector<int> temp; void dfs(int cur

QT图形视图系统 - 使用一个项目来学习QT的图形视图框架 - 终篇

QT图形视图系统 - 终篇 接上一篇&#xff0c;我们需要继续完成以下的效果&#xff1b; 先上个效果图&#xff1a; 修改背景&#xff0c;使之整体适配 上一篇我们绘制了标尺&#xff0c;并且我们修改了放大缩小和对应的背景&#xff0c;整体看来&#xff0c;我们的滚动条会和…

《面试1v1》ElasticSearch架构设计

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

设计模式行为型——状态模式

在软件开发过程中&#xff0c;应用程序中的部分对象可能会根据不同的情况做出不同的行为&#xff0c;把这种对象称为有状态的对象&#xff0c;而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时&#xff0c;其内部状态就会发生改变&…

九、Spring 声明式事务学习总结

文章目录 一、声明式事务1.1 什么是事务1.2 事务的应用场景1.3 事务的特性&#xff08;ACID&#xff09;1.4 未使用事务的代码示例1.5 配置 Spring 声明式事务学习总结 一、声明式事务 1.1 什么是事务 把一组业务当成一个业务来做&#xff1b;要么都成功&#xff0c;要么都失败…

宋浩概率论笔记(四)数字特征

本帖更新数字特征&#xff0c;包含期望、方差、相关系数等&#xff0c;要点在于记忆性质中的各种公式&#xff0c;遇到题目时能迅速利用已知条件计算答案。

Bigemap如何添加谷歌地图?

工具 Bigemap gis office地图软件 BIGEMAP GIS Office-全能版 Bigemap APP_卫星地图APP_高清卫星地图APP 打开软件&#xff0c;要提示需要授权和添加地图&#xff0c;然后去点击选择地图这个按钮&#xff0c;列表中有个添加按钮点进去选择添加地图的方式。 第一种方式&#x…

飞凌OKMX6ULL-C开发板试用

开箱体验 主要配件包括&#xff1a;USB Type-C调试线、电源线、主板。 资源下载 开发环境 飞凌提供了制作好的ubuntu18.04的镜像&#xff0c;直接到网盘下载解压即可&#xff0c;VMWare的安装可以参考网上教程&#xff0c;这里不赘述。安装好VMWare后直接打开解压出来的ubu…

macos搭建python3虚拟环境

我们知道macos自带的python版本是Python2.7, 这个版本比较老而且往往和我们的工程不兼容&#xff0c;所以就得需要我们升级Python版本&#xff0c; 我们不建议直接升级macos自带的本地Python2.7, 因为macos有一些基础软件是依赖于Python2.7的&#xff0c;如果动了遇到问题想再…

neo4j入门实例介绍

使用Cypher查询语言创建了一个图数据库&#xff0c;其中包含了电影《The Matrix》和演员Keanu Reeves、Carrie-Anne Moss、Laurence Fishburne、Hugo Weaving以及导演Lilly Wachowski和Lana Wachowski之间的关系。 CREATE (TheMatrix:Movie {title:The Matrix, released:1999,…

力扣:54. 螺旋矩阵(Python3)

题目&#xff1a; 给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣 示例&#xff1a; 示例 1&#xff1a; 输入&#xff1a;matrix [[1,…