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

目录

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 前端渲染性能:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Litt_White/article/details/130038990