06-性能优化

nobility 发布于 2022-01-05 370 次阅读


性能优化

WebpackDevServer

自动打包技术,最简单的方式使用webpack --watch命令即可监视源代码变化进行自动打包,这种方式打包文件会直接输出在硬盘上,性能较差,而且不能直接对ajax进行调试,无法模拟服务器特性

基本使用

使用webpack-dev-server工具,即可在本地启动一个node的服务器,监听源代码的变化自动打包,而且这种打包方式不会输出到硬盘上,而是在内存中,性能高,而且具有服务器的特性,首先需要使用npm install webpack-dev-server -D安装该工具,并编写以下配置

最好使用HtmlWebpackPlugin插件指定一个模板生成一个页面,否则需要在进入/webpack-dev-server/路由寻找,而且是空页面

webpack5以下使用webpack-dev-server命令进行启动,而webpack5使用webpack serve注意是serve,而不是server

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin"); //引入HtmlWebpackPlugin插件
module.exports = {
  devServer: {
    contentBase: path.join(__dirname, 'dist'),  //指定输出目录
    port: 8080, //指定端口号
    open: "Chrome",  //自动打开浏览器 要注意Chrome在macOS和Linux上是 Google Chrome,在Windows上是 Chrome
    compress: true, //开启gzips压缩
    overlay: true //编译错误时,浏览器全屏显示编译错误
  },
  entry: "./index.js",
  plugins: [
    new HtmlWebpackPlugin({
      template: "./index.html"
    })
  ]
}

代理转发

意义在于,在开发页面时写真正的AJAX请求地址,但是由于后端接口暂时不可用,可以使用代理转发功能将请求通过配置的方式修改为本地的假数据接口,在代码上线时无需对源代码进行变更

devServer: {
  contentBase: path.join(__dirname, 'dist'),  //指定输出目录
  proxy: {
    "/api": "http://127.0.0.1:3000",
     //此时请求/api下的所有请求会被代理到http://127.0.0.1:3000,比如/api/user --> http://127.0.0.1:3000/api/user
    "/root": {
      target: "http://127.0.0.1:3000",
      pathRewrite: {"^/root": ""}	//请求路径重写,并非一定替换为空字符串,也可以替换为其他值
    }
    //此时请求/api下的所有请求会被代理到http://127.0.0.1:3000,比如/root/user --> http://127.0.0.1:3000/user
  }
}

单页面路由转发

在开发单页面应用时,会使用前端路由,若前端路由使用的是path路径形式,WebpackDevServer并不会知道这是单页面,而是去请求其他页面,为了解决这种问题可以在WebpackDevServer的配置项中增加historyApiFallback="true"即可,这是的WebpackDevServer会将所有请求转发到根路由

当代码上线时也需要后端做同样的配置,将所有的请求转发到根路径

HotModuleReplacement

热模块替换的作用就是,若一个模块内容发生改变时,仅将改变的模块进行打包,而不是将所有内容进行打包,从而提升打包速度

要想使用该功能,首先需要在devServer中开启该功能,并同时引入webpack自带的插件HotModuleReplacementPlugin该功能才能生效,在高版本webpack不引入该插件也会生效,保险起见引入即可,配置如下:

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin"); //引入HtmlWebpackPlugin插件
const webpack = require('webpack');	//引入webpack
module.exports = {
  devServer: {
    hot: true, //开启热模块替换功能
    // hotOnly: true,  //热模块替换未生效时,浏览器不自动刷新
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 8080,
    open: "Chrome",
    overlay: true
  },
  entry: "./index.js",
  module: {
    rules: [{
      test: /\.css$/,
      use: [
        "style-loader",
        "css-loader" //css-loader内部已经实现了热模块替换功能
      ]
    }]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./index.html"
    }),
    new webpack.HotModuleReplacementPlugin()	//使用webopack自带插件HotModuleReplacementPlugin
  ]
}

style-loader内部已经对热模块更新进行了实现,所以可以直接使用,在高级框架中的脚手架工具中已经实现了热模块替换,所以在使用高级框架时无需写下面代码也可以实现热模块替换功能

但是webpack本事对js没有实现,所以需要手动实现,需要在js文件中使用以下代码实现

import "./test";	//引入test模块
if (module.hot) {	//若当前是启用了热模块替换功能,module.hot就会存在
  module.hot.accept("./test", () => {	//监听test模块发生变化,会重新加载test模块
      console.log("test.js文件发生了变化");	//发生变化后的对调函数
  })
}

对于入口js文件是无法做到热模块替换功能,这也是必然的,因为是入口文件引入其他模块

TreeShaking

机制

在对于部分模块引入时,TreeShaking机制会只将需要的东西引入,而不需要的东西不引入,从而减少打包后的代码的容量

/*  test.js中的内容
export function fun1(){
    console.log("fun1");
}
export function fun2(){
    console.log("fun2");
}
*/
import {fun1} from './test';    //只引入fun1
fun1();

TreeShaking机制开启

  1. webpack中只有ES6模块化支持TreeShaking机制

  2. 打包模式选择:

    • 打包模式为production时,默认开启TreeShaking功能
    • 若打包模式为development时,需要在增加一个optimization对象参数,其中usedExports值设置为true,但是为了调试方便,即使这样设置了也不会在开发模式下去除掉,只是在打包后代码中注释上了方法未使用
    optimization: {	//与基本配置项平级
      usedExports: true
    }
    
  3. package.json文件选项:

    • 添加"sideEffects": false选项,对所有模块进行TreeShaking机制
    • 该选项也可以是一个数组,用于过滤不需要TreeShaking机制的模块,比如不想摇晃掉css文件,可以设置为"sideEffects": ["*.css"],其他文件也是同理

CodeSplitting

在项目中使用到其他库文件时,webpack最终会将其打包到一个文件中,使得打包后的文件过于庞大,在网路传输时间会比较慢,解决方法就是将这个庞大的文件拆分,并行加载

多路口手动拆分

/*	jquery.js中内容 */
import $ from "jquery";	//引入jquery库
Window.$ = Window.jQuery = $;	//将变量挂在到Window对象上


/*	index.js中内容 */
console.log($);	//直接使用$即可


/*	webpack.config.js中添加多入口,但是要注意顺序,因为会影响到HtmlWebpackPlugin插件的引入顺序 */
entry: {
  jQuery: "./jquery.js",
  main: "./index.js"
}

同步引入拆分

/*	webpack.config.js中添加以下配置 */
optimization: {	//与基本配置项平级
  splitChunks: {
    chunks: "all"
  }
}

异步引入拆分

对于webpack4可能不支持以下语法,解决方法是使用@babel/plugin-syntax-dynamic-import插件,使用npm install @babel/plugin-syntax-dynamic-import -D安装该插件,该插件是babel的一个插件,需要在babel-loader配置项或babelrc文件中添加plugins: ["@babel/plugin-syntax-dynamic-import"],与presets配置项平级

/*在引入模块时使用异步引入语法*/
import(/* webpackChunkName: "jquery.js"*/"jquery").then(({	//异步引入拆分可以使用魔法注释方式指定打包后的文件名
    default: $	//兼容CommandJS的写法
}) => {
    console.log($);
})

异步拆分可以实现懒加载的功能(绑定都DOM事件上加载),懒加载的问题在于,只有在使用到该模块时才会加载,可能会有短暂的网络请求时间,对于急需要的组件用户体验不好,所以可以使用预加载,预加载就是在网络空闲时间偷偷加载到缓存中,具体参考以下代码:但是要注意Prefetch功能可能在某些浏览器中有兼容性问题

import(/* webpackPrefetch: true*/"jquery").then(({	//使用魔法注释方式指定该模块使用预加载
    default: $	//兼容CommandJS的写法
}) => {
    console.log($);
})

SplitChunkPlugin

webpack中的代码分割功能其实是集成了SplitChunkPlugin插件,上面所设置的在splitChunks中的配置实际上是SplitChunkPlugin插件的配置,若我们不配置则SplitChunkPlugin使用默认配置,默认配置如下:

optimization: {
  splitChunks: {	//代码分隔
    chunks: 'async',	//只对异步引入模块生效,所以设置为all就会对同步引入模块生效
    minSize: 20000,	//当引入模块大于20000字节时才会代码分隔
    maxSize: 0,	//当引入模块大于该值时尝试二次拆分,一般不设置,0代表没限制
    minChunks: 1,	//当引入模块同时被其他模块引用次数大于等于该值时才会做代码分隔
    maxAsyncRequests: 30,	//异步引入的模块最多分隔成30个文件
    maxInitialRequests: 30,	//同步引入的模块最多分隔成30个文件
    automaticNameDelimiter: '~',	//组和文件名做连接时的连接符
    cacheGroups: {	//缓存组
      defaultVendors: {	//默认第三方组名
        test: /[\\/]node_modules[\\/]/,	//模块路径包含该字符时
        priority: -10	//优先级
      },
      default: {	//默认组名
        minChunks: 2,	//覆盖掉前面的公共规则
        priority: -20,	//优先级
        reuseExistingChunk: true	//开启模块复用
      }
    }
  }
}

指定库名修改

一般也无需做任何其他配置,保持默认即可,若想修改同步引入模块的打包后名字时,可以在缓存组中添加匹配信息,并指定filename值即可(异步引入使用魔法注释即可)

optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {	//缓存组
      jquery: {
        test: /jquery/,	//模块路径包含该字符时
        filename: "jquery.js",
        priority: 0	//优先级
      }
    }
  }
}

简单库名修改

如果只是简单的将所有用到的第三方模块打包成一个文件,并且需要取一个自定义的名字,可以直接在output中配置

output: {
  filename: "[name].js", //输出文件名
  chunkFilename: "chunk.js",	//指定第三方模块名
  path: path.resolve(__dirname, "dist") //输出文件根目录
}

DLL

动态连接库技术是将不会变更的第三方模块单独打包,之后直接使用,不在对第三方模块进行打包,从而提升打包的速度

  1. 首先编写打包第三方库的webpack配置文件,DllPlugin是webpack自带的插件,用于生成映射文件
const path = require("path");
const webpack = require("webpack");	//引入webpack
module.exports = {
  entry: {
    jquery: ["jquery"]  //数组形式可以应用更多的库
  },
  output: {
    filename: "[name].dll.js", //输出文件名
    path: path.resolve(__dirname, "dll"), //输出文件根目录
    library: "[name]",	//将打包后的库通过全局变量形式暴露出来
  },
  plugins: [
    new webpack.DllPlugin({
      name: "[name]", //与library中暴露的变量保持一致
      path: path.resolve(__dirname, "dll/[name].manifest.json"), //输出映射文件的文件名
    })
  ]
}
  1. 为项目编写以下webpack配置,引入刚才打包好的第三方模块
    • 使用npm install add-asset-html-webpack-plugin -D安装该插件,用于向HtmlWebpackPlugin生成的HTML中添加引入script标签
    • DllReferencePlugin是webpack自带的插件,用于使用映射文件进行映射,当webpack打包时若发现映射中有要引入的模块,则不会去node_modules中找,而会使用全局变量
const HtmlWebpackPlugin = require("html-webpack-plugin"); //引入HtmlWebpackPlugin插件
const AddAssetHhtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); //引入AddAssetHhtmlWebpackPlugin插件
const path = require("path");
const webpack = require("webpack");
module.exports = {
  entry: "./index.js", //入口文件路径
  plugins: [new HtmlWebpackPlugin({ //在插件数组中实例化该插件提供的对象
    template: "./index.html" //设置html模板的路径
  }), new AddAssetHhtmlWebpackPlugin({
    filepath: path.resolve(__dirname, "dll/jquery.dll.js")  //引入打包好的第三方模块
  }),new webpack.DllReferencePlugin({
    manifest: path.resolve(__dirname, "dll/jquery.manifest.json") //使用映射文件对打包好的第三方模块进行映射
  })]
}

多进程打包

thread-loader可将放置在该loader之后的loader会在一个独立的worker池中运行,每个worker都是一个独立的node.js进程,其开销大约为600ms左右,同时会限制跨进程的数据交换,仅在耗时的操作中使用此loader,比如babel-loader

使用thread-loader首先需要使用npm install thread-loader -D安装该loader,以babel-loader为例,并编写以下规则(输出和模式采用默认了)

module.exports = {
  entry: "./index.js", //入口文件路径
  module: {
    rules: [{
      test: /\.js$/, //使用正则匹配到以js结尾的文件
      exclude: /node_modules/, //排除掉node_modules中第三方模块的js文件
      use: [{
          loader: "thread-loader", //使用thread-loader开启loader多进程
          options: {
            workers: 2, //进程数量,默认是(cpu核心数-1),若获取不到cpu核心数会回退到1
          }
        },
        {
          loader: "babel-loader", //使用babel-loader对js文件进行编译打包
          options: {
            presets: ["@babel/preset-env"], //使用@babel/preset-env做预处理
            cacheDirectory: true //开启构建缓存,第二次构建时更快
          }
        }]
    }]
  }
}

oneOf

默认情况下,源代码需要在module中的rules中的所有规则都会过一遍,并不是匹配到就跳出,所以为了减少匹配次数,可以使用oneOf进行包裹,可以达到只使用其中一个loader

module.exports = {
  entry: {
    main: "./index.js" //入口文件路径
  },
  module: {
    rules: [{	//oneOf外层可以跟其他非只匹配一次的loader
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
      },
      {
        oneOf: [{ //以下loader从上到下只会匹配到一个规则
            test: /\.css$/,
            use: ["style-loader", "css-loader"]
          },
          {
            test: /\.(scss|sass)$/,
            use: ["style-loader", "css-loader", "sass-loader"]
          }
        ]
      }
    ]
  }
}
此作者没有提供个人介绍
最后更新于 2022-01-05