懒加载和预加载
懒加载
懒加载就是,在实际项目中,某个.js文件,还没有用到,此时不进行加载,当网页中进行某个功能,有需要时在加载。复制webpack第六篇的代码分割工程文件,修改其中的config.js,进行精简。
const { resolve } = require('path');
const Htmlwebpackplugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
plugins: [
new Htmlwebpackplugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
],
optimization: {
splitChunks: { chunks: 'all' }
},
mode: 'production'
}
然后修改index.js文件
console.log('index.js文件被加载了');
// 给首页的按钮增加一个点击事件,为了实现懒加载,即用到某个js文件时才加载该文件
// 引入方式改为动态引入
document.getElementById('btn').onclick = function () {
import('./test').then(({ mul }) => {
console.log(mul(4, 5));
});
}
修改test.js代码
console.log('test.js文件被加载了');
export function mul(x, y) {
return x * y;
}
export function count(x, y) {
return x - y;
}
修改index.html代码,增加一个按钮,当点击该按钮时,test.js中的功能被需要,然后被加载。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>懒加载</h1>
<button id="btn">按钮</button>
</body>
</html>
然后终端运行npm run build ,打开打包后的index.html。发现,点击按钮后,test.js文件才被加载。
预加载
即打开网页的时候,所有的js文件都加载了,缓存到内存里,然后网页中某个功能实现需要js文件时,直接从内存中读取。修改index.js代码,增加webpackPrefetch: true 。
console.log('index.js文件被加载了');
// 给首页的按钮增加一个点击事件,为了实现懒加载,即用到某个js文件时才加载该文件
// 引入方式改为动态引入
document.getElementById('btn').onclick = function () {
// webpackPrefetch: true开启预加载
import(/*webpackChunkName:'test',webpackPrefetch: true*/'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
}
然后输入npm run build 重新打包。打开生成的index.html,可以看到,网页一打开,全部被加载了,点击按钮后,test.js文件开始被调用。
总结
- //懒加载:当文件需要使用时才加载~
- //预加载prefetch:会在使用之前,提前加载js文件
- 正常常加载可以认为是并行加载(同一时间加载多个文件)
- 预加载prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
PWA
渐进式网络应用程序(progressive web application - PWA),是一种可以提供类似于native app(原生应用程序) 体验的 web app(网络应用程序)。复制webpack第六篇的tree shaking工程文件。 实现该功能需要一个插件,输入npm i workbox-webpack-plugin -D 下载。然后在webpack.config.js中使用
const { resolve } = require('path');
const minicssextractplugin = require('mini-css-extract-plugin');
process.env.NODE_ENV = 'production'
const cssminimizerwebpackplugin = require('css-minimizer-webpack-plugin');
const Htmlwebpackplugin = require('html-webpack-plugin');
const Eslintwebpackplugin = require('eslint-webpack-plugin');
const workboxwebpackplugin = require('workbox-webpack-plugin')
// PWA:渐进式网络开发应用程序(离线可访问)
// 通过一个插件workbox-webpack-plugin
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.[contenthash:10].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [{
oneOf: [
{
test: /\.css$/,
use: [
minicssextractplugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [require('postcss-preset-env')()]
}
}
}
]
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env', {
useBuiltIns: 'usage',
corejs:
{
version: 3
},
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
cacheDirectory: true,
}
},
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
outputPath: 'imgs',
esModule: false
},
type: 'javascript/auto'
},
{
test: /\.html$/,
loader: 'html-loader',
options: {
esModule: false,
}
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)$/,
loader: 'file-loader',
options: {
outputPath: 'media',
esModule: false,
},
type: 'javascript/auto'
}
]
}
]
},
plugins: [
new minicssextractplugin({
filename: 'css/built.[contenthash:10].css'
}),
new cssminimizerwebpackplugin(
),
new Htmlwebpackplugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
new Eslintwebpackplugin({
fix: true
}),
// 使用PWA
new workboxwebpackplugin.GenerateSW({
// 进行两个设置,分别:
// 1.帮助serviceworker快速启动
//2.删除旧的serviceworkerl
// 最后生成一个serviceworker配置文件
clientsClaim: true,
skipWaiting: true
})
],
mode: 'production'
}
然后根据生成的serviceworker配置文件,在入口文件中进行注册。index.js代码
import '../css/index.css';
// 引入icon-font样式文件
import '../iconfit/iconfont.css';
// 只引入test.js中的一个mul函数
import { mul } from './test';
function sum(...args) {
return args.reduce((p, c) => p + c, 0);
}
// eslint-disable-next-line
console.log(mul(2, 2));
// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));
//注册serviceworker
//处理兼容性问题
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js ').then(() => {
console.log('sw注册成功了~');
})
.catch(() => {
console.log('sw注册失败了~');
});
});
}
因为eslint不认识 window、navigator全局变量,因此还需要在package.json中的eslintconfig配置中增加"env": { "browser": true } 这表示呢支持浏览器的全局变量。
{
"name": "webpack_production",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --mode development",
"build": "webpack --mode production"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.16.5",
"@babel/preset-env": "^7.16.5",
"babel-loader": "^8.2.3",
"core-js": "^3.20.1",
"css-loader": "^6.5.1",
"css-minimizer-webpack-plugin": "^3.3.1",
"eslint": "^8.5.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.25.3",
"eslint-webpack-plugin": "^3.1.1",
"express": "^4.17.2",
"file-loader": "^6.2.0",
"html-loader": "^3.0.1",
"html-webpack-plugin": "^5.5.0",
"less-loader": "^10.2.0",
"mini-css-extract-plugin": "^2.4.5",
"postcss-loader": "^6.2.1",
"postcss-preset-env": "^7.1.0",
"style-loader": "^3.3.1",
"url-loader": "^4.1.1",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"workbox-webpack-plugin": "^6.4.2"
},
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
},
"eslintConfig": {
"extends": "airbnb-base",
"env": {
"browser": true
}
},
"sideEffects": [
"*.css"
]
}
然后输入npm run build 进行打包。打包后看到生成两个.js文件 生成的service-worker代码必须运行在服务器上,有三种方法,一是通过nodejs编写代码,二是输入npm install http-server --save-dev 安装一个包,还要修改 package.json 的 scripts 部分,增加"start": "http-server dist" ,然后输入npm start 启动服务器,将build目录下所有资源作为静态资源暴露出去。第三种方法是输入npm install -D webpack-dev-server ,然后npx webpack serve 。这里使用第三种方法。 最后点击访问生成的网址。
把网络设置为离线,看是否还能访问。 访问正常。
多进程打包
复制上一小节工程文件。同一时间多个进程同时打包,优化打包时间。需要下载一个loader。终端输入命令npm i thread-loader -D ,修改config.js代码。
const { resolve } = require('path');
const minicssextractplugin = require('mini-css-extract-plugin');
process.env.NODE_ENV = 'production'
const cssminimizerwebpackplugin = require('css-minimizer-webpack-plugin');
const Htmlwebpackplugin = require('html-webpack-plugin');
const Eslintwebpackplugin = require('eslint-webpack-plugin');
const workboxwebpackplugin = require('workbox-webpack-plugin')
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.[contenthash:10].js',
path: resolve(__dirname, 'build')
},
module: {
rules: [{
oneOf: [
{
test: /\.css$/,
use: [
minicssextractplugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [require('postcss-preset-env')()]
}
}
}
]
},
{
test: /\.js$/,
exclude: /node_modules/,
use: [
// 开启多进程打包,进程启动大概为600ms,进程通信也有开销。只有工作消耗时间比较长,才需要
// 一般与babel loader结合使用
'thread-loader',
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env', {
useBuiltIns: 'usage',
corejs:
{
version: 3
},
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
cacheDirectory: true,
}
}
]
},
{
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
outputPath: 'imgs',
esModule: false
},
type: 'javascript/auto'
},
{
test: /\.html$/,
loader: 'html-loader',
options: {
esModule: false,
}
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)$/,
loader: 'file-loader',
options: {
outputPath: 'media',
esModule: false,
},
type: 'javascript/auto'
}
]
}
]
},
plugins: [
new minicssextractplugin({
filename: 'css/built.[contenthash:10].css'
}),
new cssminimizerwebpackplugin(
),
new Htmlwebpackplugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
new Eslintwebpackplugin({
fix: true
}),
// 使用PWA
new workboxwebpackplugin.GenerateSW({
// 进行两个设置,分别:
// 1.帮助serviceworker快速启动
//2.删除旧的serviceworkerl
// 最后生成一个serviceworker配置文件
clientsClaim: true,
skipWaiting: true
})
],
mode: 'production'
}
终端输入npm run build 进行打包,一般当项目文件比较大时,这个功能的优势才会更明显。
externals
复制webpack5第一篇的打包html资源工程。并重命名。externals是防止将某些 import 的包(package)打包到 build(存放打包后文件的地方)中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。例如,从 CDN 引入 jQuery,而不是把它打包。 复制好的工程文件目录如下
修改config.js代码
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/index.js', output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'production',
// 外部扩展(externals)
// 防止将某些 import 的包(package)打包到 built 中,
externals: {
jquery: 'jQuery'
}
}
修改index.js代码,使用jquery
import $ from 'jquery';
console.log($);
function add(x, y) {
return x + y;
}
console.log(add(1, 2));
然后输入npm run build 我们发现生成的built.js文件大小是312bytes。如果把jquery也打包的话,文件大小肯定远远大于这个值。
最后记得要在index.html中手动引入jquery。因为我们没有打包jquery,被externals设置排除了,手动引入后,才能正常使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1 id="title">hello html</h1>
<script src="https://code.jquery.com/jquery-3.1.0.js"
integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk=" crossorigin="anonymous">
</script>
</body>
</html>
在重新打包一次。在浏览器打开生成的index.html文件。可以看到此时的built.js文件是312bytes,网页的功能也正常。
DLL(动态链接库)
复制webpack5第一篇的打包html资源工程。并重命名。DLL功能就是单独打包,把不同的文件最后打包到不同的文件,即多对多的关系。在复制的工程文件夹下新增webpack.dll.js文件。其代码如下
/*
使用dll技术,对某些库(第三方库:jquery、react、vue. . . )进行单独打包
当你运行webpack时,默认查找webpack.config.js配置文件
而我们需要运行webpack.dll.js文件
所以输入命令: webpack --config webpack.dll.js,进行修改
*/
const { resolve } = require('path');
// webpack自带的插件
const webpack = require('webpack')
module.exports = {
entry: {
//最终打包生成的[name] --> jquery
// ['jquery']-- > 要打包的库是jquery
jquery: ['jquery']
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'dll'),
library: '[name]_[hash:10]'// 打包的库里面向外暴露出去的内容叫什么名字
},
plugins: [
// 使用webpack自带的插件,打包生成一个manifest.json文件,提供和jquery的映射
new webpack.DllPlugin({
name: '[name]_[hash:10]',//映射库的暴露的内容名称
path: resolve(__dirname, 'dll/manifest.json')//输出文件路径
})
],
mode: 'production'
}
终端输入npm i jquery --save 下载jquery包。然后修改package.json中代码
{
"devDependencies": {
"html-webpack-plugin": "^5.5.0",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1"
},
"name": "webpack_html",
"version": "1.0.0",
"main": "webpack.config.js",
"dependencies": {
"acorn": "^8.6.0",
"acorn-import-assertions": "^1.8.0",
"ajv": "^6.12.6",
"ajv-keywords": "^3.5.2",
"ansi-regex": "^5.0.1",
"boolbase": "^1.0.0",
"browserslist": "^4.19.1",
"buffer-from": "^1.1.2",
"camel-case": "^4.1.2",
"caniuse-lite": "^1.0.30001292",
"chrome-trace-event": "^1.0.3",
"clean-css": "^5.2.2",
"commander": "^8.3.0",
"css-select": "^4.2.0",
"css-what": "^5.1.0",
"dom-converter": "^0.2.0",
"dom-serializer": "^1.3.2",
"domelementtype": "^2.2.0",
"domhandler": "^4.3.0",
"domutils": "^2.8.0",
"dot-case": "^3.0.4",
"electron-to-chromium": "^1.4.28",
"enhanced-resolve": "^5.8.3",
"entities": "^2.2.0",
"es-module-lexer": "^0.9.3",
"escalade": "^3.1.1",
"eslint-scope": "^5.1.1",
"esrecurse": "^4.3.0",
"estraverse": "^4.3.0",
"events": "^3.3.0",
"fast-deep-equal": "^3.1.3",
"fast-json-stable-stringify": "^2.1.0",
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.8",
"has-flag": "^4.0.0",
"he": "^1.2.0",
"html-minifier-terser": "^6.1.0",
"htmlparser2": "^6.1.0",
"jest-worker": "^27.4.5",
"jquery": "^3.6.0",
"json-parse-better-errors": "^1.0.2",
"json-schema-traverse": "^0.4.1",
"loader-runner": "^4.2.0",
"lodash": "^4.17.21",
"lower-case": "^2.0.2",
"merge-stream": "^2.0.0",
"mime-db": "^1.51.0",
"mime-types": "^2.1.34",
"neo-async": "^2.6.2",
"no-case": "^3.0.4",
"node-releases": "^2.0.1",
"nth-check": "^2.0.1",
"param-case": "^3.0.4",
"pascal-case": "^3.1.2",
"picocolors": "^1.0.0",
"pretty-error": "^4.0.0",
"punycode": "^2.1.1",
"randombytes": "^2.1.0",
"relateurl": "^0.2.7",
"renderkid": "^3.0.0",
"safe-buffer": "^5.2.1",
"schema-utils": "^3.1.1",
"serialize-javascript": "^6.0.0",
"source-map": "^0.6.1",
"source-map-support": "^0.5.21",
"strip-ansi": "^6.0.1",
"supports-color": "^8.1.1",
"tapable": "^2.2.1",
"terser": "^5.10.0",
"terser-webpack-plugin": "^5.3.0",
"tslib": "^2.3.1",
"uri-js": "^4.4.1",
"utila": "^0.4.0",
"watchpack": "^2.3.1",
"webpack-sources": "^3.2.2"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --mode development",
"build": "webpack --mode production",
"dill":"webpack --config webpack.dll.js"
},
"author": "",
"license": "ISC",
"description": ""
}
然后终端输入:npm run dill 。这样就修改了打包时默认的配置文件,变成了webpack.dll.js。 至此, 我们已经把jquery单独打包出来了到一个文件夹中,那么以后再打包时,就可以不用在打包jquery了。想打包其他非官方modules时,需要在修改config.js代码。
/*工作流程
loader: 1下载 2使用(配置loader)
plugins: 1.下载 2.引入 3使用
*/
const { resolve } = require('path');
// 引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack')
module.exports = {
entry: './src/index.js', output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
]
},
plugins: [
//plugins的配置
// html-webpack-plugin配置
// 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(S/cSs)
new HtmlWebpackPlugin({
//复制'./src/index.html’文件,并自动引入打包输出的所有资源(JS/cSs)
template: './src/index.html'
}),
// 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
})
],
mode: 'development'
}
然后我们在index.js引入jquery代码。
import $ from 'jquery'
console.log($);
function add(x, y) {
return x + y;
}
console.log(add(1, 2));
如果此时不修改config.js中代码,直接进行生产环境下的打包,npm run build ,则最后的打包文件还是会把jquery与自己写的代码杂糅起来。使用了webpack.DllReferencePlugin插件后,输入npm run build ,查看效果。此时的built.js中没有柔和jquery代码。体积很小。 那么我们需要用jquery,该怎么办呢,此时需要另一个插件,输入npm i add-asset-html-webpack-plugin -D .该插件将某个文件打包输出去,并在html中自动引入该资源。然后在config.js中使用。
/*工作流程
loader: 1下载 2使用(配置loader)
plugins: 1.下载 2.引入 3使用
*/
const { resolve } = require('path');
// 引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
module.exports = {
entry: './src/index.js', output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
]
},
plugins: [
//plugins的配置
// html-webpack-plugin配置
// 功能:默认会创建一个空的HTML,自动引入打包输出的所有资源(S/cSs)
new HtmlWebpackPlugin({
//复制'./src/index.html’文件,并自动引入打包输出的所有资源(JS/cSs)
template: './src/index.html'
}),
// 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 将某个文件打包输出去,并在html中自动引入该资源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js'),
outputPath: "auto"
})
],
mode: 'development'
}
此时在重新打包一次,npm run build 。
此时在运行打包后的html文件就没问题了。
总结
我们通过一个webpack.dll.js先单独打包jquery文件,然后在webpack.config.js中使用了插件webpack.DllReferencePlugin,告诉webpack,在生产环境打包时,不需要再对jquery打包了,然后又使用了插件AddAssetHtmlWebpackPlugin,告诉webpack,将之前单独打包的jquery自动输出并引入到html文件中去。就可以避免在修改配置后再打包时,还会重复打包jquery,节省了时间。
性能优化总结
# WEBPACK性能优化
* 开发环境性能优化
* 生产环境性能优化
## 开发环境性能优化
* 优化打包构建速度
*HMR功能
* 优化代码调试
*source-map
## 生产环境性能优化
* 优化打包构建速度
*oneOf
*babel缓存
*多进程打包
*externals
*dll
* 优化代码运行的性能
*缓存(hash-chunkhash-contenthash)
*tree shaking
*代码分割(code split)
*懒加载/预加载
*PWA
webpack详细配置之entry
新建工程文件目录如下 config.js中代码
const { resolve } = require('path');
const HtmlwebpackPlugin = require('html-webpack-plugin')
/**
* entry : 入口起点有三种值
* 1. string
* 2. array
* 3. object
*/
module.exports = {
entry: './src/index.js ',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlwebpackPlugin()
],
mode: 'development'
};
index.js代码
import add from './add';
import count from './count'
console.log('index.js文件被加载了');
console.log(add(1, 2));
console.log(count(2, 3));
add.js代码
function add(x, y) {
return x + y;
}
export default add;
count.js代码
function count(x, y) {
return x - y;
}
export default count;
然后在终端命令依次输入:
npm init
webpack_entry
npm i webpack webpack-cli -D
npm i html-webpack-plugin -D
然后此时工程目录如下 修改package.json代码
{
"name": "webpack_entry",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --mode development",
"build": "webpack --mode production"
},
"author": "",
"license": "ISC",
"devDependencies": {
"html-webpack-plugin": "^5.5.0",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1"
}
}
然后终端命令输入npm run dev 。build文件夹下生成了两个文件。 所以说,entry入口起点,使用string形式,输入一个单文件,则会打包形成一个built.js文件,之前自己写的add.js,count.js全杂糅进来了。
entry: './src/index.js', //string写法
修改config.js文件,改为数组形式的多入口文件配置,则最终只会形成一个built.js文件,之前自己写的add.js,count.js全杂糅进来了。在HMR功能中使用该方法。
entry: ['./src/index.js', './src/add.js'] //array写法
修改config.js文件,改为对象形式(key:value)写法,有几个入口文件,就打包成几个文件,打包后文件的名称,为key的名称。
entry: {
index: './src/index.js',
add: './src/add.js'
},//对象object形式写法
最后注意object形式有个特殊用法。
// object有个特殊用法
{
//index数组中的所有入口文件最终只会形成一个chunk,打包输出出去只有一个bundle文件。
index: [ './src/index.js', './src/count.js'],
//add中的入口文件形成一个chunk,打包输出一个bundle文件。
add: './src/add.js'
}
|