1. Preload & Prefetch

为什么需要Preload & Prefetch

  • 我们前面已经做了代码分割,同时会使用 import 动态导入语法来进行代码按需加载(我们也叫懒加载,比如路由懒加载就是这样实现的)。
  • 但是加载速度还不够好,比如:是用户点击按钮时才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。
  • 我们想在浏览器空闲时间,加载后续需要使用的资源。我们就需要用上 PreloadPrefetch 技术。

什么是Preload & Prefetch

  • Preload:告诉浏览器立即加载资源。

  • Prefetch:告诉浏览器在空闲时才开始加载资源。

两者的共同点

  • 都只会加载资源,并不执行。
  • 都有缓存。

两者的不同点

  • Preload加载优先级高,Prefetch加载优先级低。
  • Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源。

总结:

  • 当前页面优先级高的资源用 Preload 加载。
  • 下一个页面需要使用的资源用 Prefetch 加载。

它们的问题:兼容性较差。

  • 我们可以去 Can I Use 网站查询 API 的兼容性问题。
  • Preload 相对于 Prefetch 兼容性好一点。

官方网站

基本使用

  1. 下载包
npm i @vue/preload-webpack-plugin -D
  1. 配置webpack.prod.js
// 使用的是commonjs 的语法格式[node.js]
const path = require("path");//node.js中的核心模块,专门用于处理路径问题
// 获取os内置模块
const os = require("os");
// 引入eslint
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
// 引入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 引入MiniCssExtractPlugin插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 引入CssMinimizerPlugin插件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 引入Terser内置插件
const TerserPlugin = require("terser-webpack-plugin");
// 引入图片压缩插件
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
// 引入预加载插件
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

// 获取cpu核数
const threads = os.cpus().length;

// 设置一个函数用来获取样式处理的loader(提高代码复用率)
function getStyleLoader(pre){//pre为其他的loader,如less-loader
return [
// *************************************************************************
MiniCssExtractPlugin.loader, //将style-loder改成MiniCssExtractPlugin.loader
// ************************************************************************
"css-loader",//将css资源编译成common.js的模块到js当中

// postcss-loader处理css样式的兼容性问题(放在cssloder后面,lessloder前面)*****
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env", // 能解决大多数样式兼容性问题
],
},
},
},
// ************************************************************************
pre,
].filter(Boolean)//设置一个布尔值的filter来过滤掉undefined(存css样式不需要pre[即其他的loader])
}

module.exports = {
// 入口
// 相对路径和绝对路径都行
// 单入口
entry: "./src/main.js",//相对路径
// 多入口
// entry: {
// main: "./src/main.js",
// app: "./src/app.js",
// },
// 输出
output:{
// 所有文件的输出目录,必须是绝对路径
// path.resolve()方法返回一个绝对路径
// __dirname为node.js中的变量,代表当前文件的文件夹名(就是这个文件夹的webpack_code)
path: path.resolve(__dirname , "../dist"),//相较于开发模式,生产模式需要输出
// js文件(入口文件)输出的文件名(打包后在输出路径当中生成的文件夹名)
filename:"static/js/main.js",//所以我们这里改成js资源就输出到一个js文件夹当中

// 设置文件的输出名字(动态按需引入的js文件等)*******************************
chunkFilename: 'static/js/[name].js',//设置打包输出的其他文件命名
// ***********************************************************************

clean: true,//需要输出就需要clean(自动清空上次打包内容) 原理:在打包前,将path整个目录内容清空,在进行打包
},
// 加载器
module:{
rules:[
// loder的配置
{
// oneOf配置, 每个文件只能被其中一个loader处理(第一个遇到的)************************
oneOf: [
// 1.处理css资源
{
// 用来匹配 .css 结尾的文件
test: /\.css$/,
// use 数组里面 Loader 执行顺序是从右到左
use: getStyleLoader()
},
// 2.处理less资源
{
test: /\.less$/,
// loader:xxx => 自能使用一个loader
use: getStyleLoader("less-loader")
},
// 3.处理图片资源
{
test: /\.(png|jpe?g|gif|webp)$/,//正则判断图片后缀
type: "asset",
parser:{
dataUrlCondition:{
// 将小于10kb的图片转化为base64
// 优点:减小请求数量 缺点:原图片的体积会变大(故大体积突变不会使用这种方法)
maxSize: 10*1024//10kb(大体积图片不会使用这种方法)
}
},
generator: {
// 将图片文件输出到 static/imgs 目录中
// 将图片文件命名 [hash:8][ext][query]
// [hash:8]: hash值取8位
// [ext]: 使用之前的文件扩展名
// [query]: 添加之前的query参数
filename: 'static/imgs/[hash:8][ext][query]'
}
},
// 4.处理字体资源
{
test: /\.(ttf|woff2?)$/,
type: "asset/resource",//这里的是改成"asset/resource"
generator: {
filename: "static/media/[hash:8][ext][query]",
},
},
// 配置babel***********************************************
{
test: /\.js$/,//检查匹配以js结尾的文件
// Include/Exclude*******************************************
// exclude: /node_modules/, // 排除node_modules代码不编译
include: path.resolve(__dirname, "../src"), // 也可以用包含(两者只能用其一)
// **********************************************************

use: [
{//开启多线程编译打包***************************
loader: "thread-loader", // 开启多进程
options: {
workers: threads, // 数量
},
// *****************************************
},
{
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启babel编译缓存
},
},
],
// ********************************************************
},
]
// ****************************************************************************
}
],
},
// 插件
plugins:[
// plugin的配置

// eslint配置
new ESLintWebpackPlugin({
// 指定检查文件的根目录(src目录下的所有文件的语法)
context: path.resolve(__dirname, "../src"),
// Include/Exclude******************
exclude: "node_modules", // 默认值
// ********************************
cache: true, // 开启缓存
// 缓存目录
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
// 开启多线程打包编译***************
threads, // 开启多进程
// *******************************
}),

// HtmlWebpackPlugin配置*******************************************************
new HtmlWebpackPlugin({
// 以 public/index.html 为模板创建文件
// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
template: path.resolve(__dirname, "../public/index.html"),
}),
// ****************************************************************************

// 提取css成单独文件
new MiniCssExtractPlugin({
// 定义输出文件名和输出路径
filename: "static/css/main.css",
}),

// 预加载插件*************************************************
new PreloadWebpackPlugin({
rel: "preload", // preload兼容性更好
as: "script",

// rel: 'prefetch' // prefetch兼容性更差
}),
// *******************************************************

// css压缩(新版本一般是放在下面的optimization中,压缩,优化均是)*********************
// new CssMinimizerPlugin(),
// ******************************************************************************
],

// 开启多线程需要重新手写 Terser 内置模块*********************************************
// webpack5一般指定压缩地方为这里,上面也可以
optimization: {
minimize: true,
minimizer: [
// css压缩
// css压缩也可以写到optimization.minimizer里面,效果一样的
new CssMinimizerPlugin(),//压缩css

//js压缩
// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
new TerserPlugin({//内置模块压缩js
parallel: threads // 开启多进程
}),

//图片压缩*******************************************
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
["gifsicle", { interlaced: true }],
["jpegtran", { progressive: true }],
["optipng", { optimizationLevel: 5 }],
[
"svgo",
{
plugins: [
"preset-default",
"prefixIds",
{
name: "sortAttrs",
params: {
xmlnsOrder: "alphabetical",
},
},
],
},
],
],
},
},
}),
// *************************************************
],

// 代码分割配置
splitChunks: {
// 默认分割即可(spa单页面应用)
chunks: "all", // 对所有模块都进行分割
// 以下是默认值
// minSize: 20000, // 分割代码最小的大小
// minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
// minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
// maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
// maxInitialRequests: 30, // 入口js文件最大并行请求数量
// enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
// cacheGroups: { // 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
// default: { // 其他没有写的配置会使用上面的默认值
// minChunks: 2, // 这里的minChunks权重更大
// priority: -20,
// reuseExistingChunk: true,
// },
// },
},
},
// **********************************************************************************
/*
生产模式不需要开发服务器(devServer)
*/
// 模式
mode:"production",//生产模式
devtool: "source-map",//sourceMap生产模式
};

结果展示:(这里展示preload,prefetch基本一致)

  • 输出的html模板
    image

  • 在浏览器开发工具中可以看到,浏览器一刷新,sayhi.js就加载了
    image

2. Core-js

为什么需要Core-js?

  • 过去我们使用 babeljs 代码进行了兼容性处理,其中使用@babel/preset-env 智能预设来处理兼容性问题。
  • 它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理。
  • 所以此时我们 js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。所以我们想要将 js 兼容性问题彻底解决

什么是Core-js

  • core-js 是专门用来做 ES6 以及以上 API 的 polyfill
  • polyfill翻译过来叫做垫片/补丁。就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性。

基本使用

  1. 修改 main.js(添加es7新语法(includes))
// 引入js文件
import count from './js/count'
import sum from './js/sum'
// 引入css资源
import './css/index.css'
// 引入less资源
import './less/index.less'
// 引入字体样式(切记是字体样式并非字体)
import './css/iconfont.css'

console.log(count(3 , 1));
console.log(sum(1,2,3,4,5,6));

// 按钮点击事件实现 按需加载 ,动态导入
document.getElementById('btn').onclick = function(){
// 动态导入 --> 实现按需加载
// 即使只被引用了一次,也会代码分割
// 切记一定要使用import动态导入

// eslint会对动态导入语法报错,需要修改eslint配置文件
// webpackChunkName: "sayhi":这是webpack动态导入模块命名的方式
// "sayhi"将来就会作为[name]的值显示。
import( /*webpackChunkName: "sayhi"*/ "./js/sayhi.js").then((res)=>{
console.log(res);
})
}

// 判断是否支持HMR功能
if (module.hot) {
module.hot.accept("./js/count.js");//开启对应路径中js的热模替换
module.hot.accept("./js/sum.js");//开启对应路径中js的热模替换
}

// 添加 es7 新语法 includes
const a = [1 , 2 , 3 , 4]
console.log(a.includes(1));
  • 此时 Eslint 会对 includes报错。
  1. 修改配置文件

    • 下载包
    npm i @babel/eslint-parser -D
    • 创建.eslintrc.js
    module.exports = {
    // 继承 Eslint 规则
    extends: ["eslint:recommended"],
    parser: "@babel/eslint-parser", // 支持最新的最终 ECMAScript 标准
    env: {
    node: true, // 启用node中全局变量
    browser: true, // 启用浏览器中全局变量
    },
    plugins: ["import"], // 解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的
    parserOptions: {
    ecmaVersion: 6, // es6
    sourceType: "module", // es module
    },
    rules: {
    "no-var": 2, // 不能使用 var 定义变量
    },
    };
  2. 使用core-js

  • 下载包
npm i core-js
  • 方法一: 手动全部引入(优点:方便快捷 , 缺点:打包生成的项目体积大)
// 全部引入 core.js
import "core-js";

// 引入js文件
import count from './js/count'
import sum from './js/sum'
// 引入css资源
import './css/index.css'
// 引入less资源
import './less/index.less'
// 引入字体样式(切记是字体样式并非字体)
import './css/iconfont.css'

console.log(count(3 , 1));
console.log(sum(1,2,3,4,5,6));

// 按钮点击事件实现 按需加载 ,动态导入
document.getElementById('btn').onclick = function(){
// 动态导入 --> 实现按需加载
// 即使只被引用了一次,也会代码分割
// 切记一定要使用import动态导入

// eslint会对动态导入语法报错,需要修改eslint配置文件
// webpackChunkName: "sayhi":这是webpack动态导入模块命名的方式
// "sayhi"将来就会作为[name]的值显示。
import( /*webpackChunkName: "sayhi"*/ "./js/sayhi.js").then((res)=>{
console.log(res);
})
}

// 判断是否支持HMR功能
if (module.hot) {
module.hot.accept("./js/count.js");//开启对应路径中js的热模替换
module.hot.accept("./js/sum.js");//开启对应路径中js的热模替换
}

// 添加 es7 新语法 includes
const a = [1 , 2 , 3 , 4]
console.log(a.includes(1));
  • 方法二: 手动按需引入(优点:项目体积小 , 缺点: 引入繁琐)
// 全部引入 core.js
// import "core-js";
// 按需引入 core.js
import "core-js/es/array";

// 引入js文件
import count from './js/count'
import sum from './js/sum'
// 引入css资源
import './css/index.css'
// 引入less资源
import './less/index.less'
// 引入字体样式(切记是字体样式并非字体)
import './css/iconfont.css'

console.log(count(3 , 1));
console.log(sum(1,2,3,4,5,6));

// 按钮点击事件实现 按需加载 ,动态导入
document.getElementById('btn').onclick = function(){
// 动态导入 --> 实现按需加载
// 即使只被引用了一次,也会代码分割
// 切记一定要使用import动态导入

// eslint会对动态导入语法报错,需要修改eslint配置文件
// webpackChunkName: "sayhi":这是webpack动态导入模块命名的方式
// "sayhi"将来就会作为[name]的值显示。
import( /*webpackChunkName: "sayhi"*/ "./js/sayhi.js").then((res)=>{
console.log(res);
})
}

// 判断是否支持HMR功能
if (module.hot) {
module.hot.accept("./js/count.js");//开启对应路径中js的热模替换
module.hot.accept("./js/sum.js");//开启对应路径中js的热模替换
}

// 添加 es7 新语法 includes
const a = [1 , 2 , 3 , 4]
console.log(a.includes(1));
  • 自动按需引入(推荐使用)

    • 这种方法不需要在main.js中引入core.js,我们只需要在babel.config.js中的presets属性中设置相关项即可

    • 官方文档

  • babel.config.js

module.exports = {
// 智能预设:能够编译ES6语法
presets: [
[
"@babel/preset-env",
// 按需加载core-js的polyfill
{ useBuiltIns: "usage", corejs: { version: "3", proposals: true } },
],
],
};
  • 此时就会自动根据我们代码中使用的语法,来按需加载相应的 polyfill 了。

3. PWA

为什么需要PWA

  • 开发 Web App 项目,项目一旦处于网络离线情况,就没法访问了。
  • 我们希望给项目提供离线体验。

PWA是什么?

  • 渐进式网络应用程序(progressive web application - PWA):是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。

  • 其中最重要的是,在 离线(offline) 时应用程序能够继续运行功能。

  • 内部通过 Service Workers 技术实现的。

  • 官方文档

如何使用

  1. 下载包
npm i workbox-webpack-plugin -D
  1. 修改配置文件
// 使用的是commonjs 的语法格式[node.js]
const path = require("path");//node.js中的核心模块,专门用于处理路径问题
// 获取os内置模块
const os = require("os");
// 引入eslint
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
// 引入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 引入MiniCssExtractPlugin插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 引入CssMinimizerPlugin插件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 引入Terser内置插件
const TerserPlugin = require("terser-webpack-plugin");
// 引入图片压缩插件
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
// 引入预加载插件
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
// 引入离线缓存插件
const WorkboxPlugin = require("workbox-webpack-plugin");

// 获取cpu核数
const threads = os.cpus().length;

// 设置一个函数用来获取样式处理的loader(提高代码复用率)
function getStyleLoader(pre){//pre为其他的loader,如less-loader
return [
// *************************************************************************
MiniCssExtractPlugin.loader, //将style-loder改成MiniCssExtractPlugin.loader
// ************************************************************************
"css-loader",//将css资源编译成common.js的模块到js当中

// postcss-loader处理css样式的兼容性问题(放在cssloder后面,lessloder前面)*****
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env", // 能解决大多数样式兼容性问题
],
},
},
},
// ************************************************************************
pre,
].filter(Boolean)//设置一个布尔值的filter来过滤掉undefined(存css样式不需要pre[即其他的loader])
}

module.exports = {
// 入口
// 相对路径和绝对路径都行
// 单入口
entry: "./src/main.js",//相对路径
// 多入口
// entry: {
// main: "./src/main.js",
// app: "./src/app.js",
// },
// 输出
output:{
// 所有文件的输出目录,必须是绝对路径
// path.resolve()方法返回一个绝对路径
// __dirname为node.js中的变量,代表当前文件的文件夹名(就是这个文件夹的webpack_code)
path: path.resolve(__dirname , "../dist"),//相较于开发模式,生产模式需要输出
// js文件(入口文件)输出的文件名(打包后在输出路径当中生成的文件夹名)
filename:"static/js/main.js",//所以我们这里改成js资源就输出到一个js文件夹当中

// 设置文件的输出名字(动态按需引入的js文件等)*******************************
chunkFilename: 'static/js/[name].js',//设置打包输出的其他文件命名
// ***********************************************************************

clean: true,//需要输出就需要clean(自动清空上次打包内容) 原理:在打包前,将path整个目录内容清空,在进行打包
},
// 加载器
module:{
rules:[
// loder的配置
{
// oneOf配置, 每个文件只能被其中一个loader处理(第一个遇到的)************************
oneOf: [
// 1.处理css资源
{
// 用来匹配 .css 结尾的文件
test: /\.css$/,
// use 数组里面 Loader 执行顺序是从右到左
use: getStyleLoader()
},
// 2.处理less资源
{
test: /\.less$/,
// loader:xxx => 自能使用一个loader
use: getStyleLoader("less-loader")
},
// 3.处理图片资源
{
test: /\.(png|jpe?g|gif|webp)$/,//正则判断图片后缀
type: "asset",
parser:{
dataUrlCondition:{
// 将小于10kb的图片转化为base64
// 优点:减小请求数量 缺点:原图片的体积会变大(故大体积突变不会使用这种方法)
maxSize: 10*1024//10kb(大体积图片不会使用这种方法)
}
},
generator: {
// 将图片文件输出到 static/imgs 目录中
// 将图片文件命名 [hash:8][ext][query]
// [hash:8]: hash值取8位
// [ext]: 使用之前的文件扩展名
// [query]: 添加之前的query参数
filename: 'static/imgs/[hash:8][ext][query]'
}
},
// 4.处理字体资源
{
test: /\.(ttf|woff2?)$/,
type: "asset/resource",//这里的是改成"asset/resource"
generator: {
filename: "static/media/[hash:8][ext][query]",
},
},
// 配置babel***********************************************
{
test: /\.js$/,//检查匹配以js结尾的文件
// Include/Exclude*******************************************
// exclude: /node_modules/, // 排除node_modules代码不编译
include: path.resolve(__dirname, "../src"), // 也可以用包含(两者只能用其一)
// **********************************************************

use: [
{//开启多线程编译打包***************************
loader: "thread-loader", // 开启多进程
options: {
workers: threads, // 数量
},
// *****************************************
},
{
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启babel编译缓存
},
},
],
// ********************************************************
},
]
// ****************************************************************************
}
],
},
// 插件
plugins:[
// plugin的配置

// eslint配置
new ESLintWebpackPlugin({
// 指定检查文件的根目录(src目录下的所有文件的语法)
context: path.resolve(__dirname, "../src"),
// Include/Exclude******************
exclude: "node_modules", // 默认值
// ********************************
cache: true, // 开启缓存
// 缓存目录
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
// 开启多线程打包编译***************
threads, // 开启多进程
// *******************************
}),

// HtmlWebpackPlugin配置*******************************************************
new HtmlWebpackPlugin({
// 以 public/index.html 为模板创建文件
// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
template: path.resolve(__dirname, "../public/index.html"),
}),
// ****************************************************************************

// 提取css成单独文件
new MiniCssExtractPlugin({
// 定义输出文件名和输出路径
filename: "static/css/main.css",
}),

// 预加载插件*************************************************
new PreloadWebpackPlugin({
rel: "preload", // preload兼容性更好
as: "script",

// rel: 'prefetch' // prefetch兼容性更差
}),
// *******************************************************

// 离线缓存插件(使webapp能在离线的情况下使用基本功能)**********
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true,
}),
// **********************************************************

// css压缩(新版本一般是放在下面的optimization中,压缩,优化均是)*********************
// new CssMinimizerPlugin(),
// ******************************************************************************
],

// 开启多线程需要重新手写 Terser 内置模块*********************************************
// webpack5一般指定压缩地方为这里,上面也可以
optimization: {
minimize: true,
minimizer: [
// css压缩
// css压缩也可以写到optimization.minimizer里面,效果一样的
new CssMinimizerPlugin(),//压缩css

//js压缩
// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
new TerserPlugin({//内置模块压缩js
parallel: threads // 开启多进程
}),

//图片压缩*******************************************
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
["gifsicle", { interlaced: true }],
["jpegtran", { progressive: true }],
["optipng", { optimizationLevel: 5 }],
[
"svgo",
{
plugins: [
"preset-default",
"prefixIds",
{
name: "sortAttrs",
params: {
xmlnsOrder: "alphabetical",
},
},
],
},
],
],
},
},
}),
// *************************************************
],

// 代码分割配置
splitChunks: {
// 默认分割即可(spa单页面应用)
chunks: "all", // 对所有模块都进行分割
// 以下是默认值
// minSize: 20000, // 分割代码最小的大小
// minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
// minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
// maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
// maxInitialRequests: 30, // 入口js文件最大并行请求数量
// enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
// cacheGroups: { // 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
// default: { // 其他没有写的配置会使用上面的默认值
// minChunks: 2, // 这里的minChunks权重更大
// priority: -20,
// reuseExistingChunk: true,
// },
// },
},
},
// **********************************************************************************
/*
生产模式不需要开发服务器(devServer)
*/
// 模式
mode:"production",//生产模式
devtool: "source-map",//sourceMap生产模式
};
  1. 修改main.js
// 全部引入 core.js
// import "core-js";
// 按需引入 core.js
// import "core-js/es/array";

// 引入js文件
import count from './js/count'
import sum from './js/sum'
// 引入css资源
import './css/index.css'
// 引入less资源
import './less/index.less'
// 引入字体样式(切记是字体样式并非字体)
import './css/iconfont.css'

console.log(count(3 , 1));
console.log(sum(1,2,3,4,5,6));

// 按钮点击事件实现 按需加载 ,动态导入
document.getElementById('btn').onclick = function(){
// 动态导入 --> 实现按需加载
// 即使只被引用了一次,也会代码分割
// 切记一定要使用import动态导入

// eslint会对动态导入语法报错,需要修改eslint配置文件
// webpackChunkName: "sayhi":这是webpack动态导入模块命名的方式
// "sayhi"将来就会作为[name]的值显示。
import( /*webpackChunkName: "sayhi"*/ "./js/sayhi.js").then((res)=>{
console.log(res);
})
}

// 判断是否支持HMR功能
if (module.hot) {
module.hot.accept("./js/count.js");//开启对应路径中js的热模替换
module.hot.accept("./js/sum.js");//开启对应路径中js的热模替换
}

// 添加 es7 新语法 includes
const a = [1 , 2 , 3 , 4]
console.log(a.includes(1));

// 配置serviceWorker 使浏览器能在断网的情况下 webapp仍能使用基本功能
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("/service-worker.js")
.then((registration) => {
console.log("SW registered: ", registration);
})
.catch((registrationError) => {
console.log("SW registration failed: ", registrationError);
});
});
}
  1. 运行指令
npm run build

此时如果通过 VSCode 访问打包后页面,在浏览器控制台出现 SW registration failed

  • 因为我们打开的访问路径是:http://127.0.0.1:5500/dist/index.html。此时页面会去请求 service-worker.js 文件,请求路径是:http://127.0.0.1:5500/service-worker.js,这样找不到会 404。

  • 实际 service-worker.js 文件路径是:http://127.0.0.1:5500/dist/service-worker.js

  1. 解决路径问题
  • 下载包
npm i serve -g

serve 也是用来启动开发服务器来部署代码查看效果的。

  • 运行指令
serve dist

此时通过 serve 启动的服务器我们 service-worker 就能注册成功了。

结果展示:(离线后仍然可以进行基本输出)

image

webpack高级篇总结

我们从 4 个角度对 webpack 和代码进行了优化:

  1. 提升开发体验
  • 使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。
  1. 提升 webpack 提升打包构建速度
  • 使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。
  • 使用 OneOf 让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。
  • 使用 Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。
  • 使用 Cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。
  • 使用 Thead 多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)
  1. 减少代码体积
  • 使用 Tree Shaking 剔除了没有使用的多余代码,让代码体积更小。
  • 使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。
  • 使用 Image Minimizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)
  1. 优化代码运行性能
  • 使用 Code Split 对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。
  • 使用 Preload / Prefetch 对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。
  • 使用 Network Cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。
  • 使用 Core-js 对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。
  • 使用 PWA 能让代码离线也能访问,从而提升用户体验。