npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

xhs-test

v1.0.0

Published

this is a test project for stduy,so don't to download,thanks!!!

Downloads

5

Readme

性能优化概述

性能优化主要体现在以下三个方面: alt text 构建性能 这里所说的构建性能是指开发阶段的构建性能,而不是生产环境的构建性能。 优化的目标是,降低从打包开始到代码效果呈现所经过的事件 构建性能会影响开发效率。构建性能越高,开发过程中的时间浪费越少 传输性能 传输性能是指打包后的js代码传输到浏览器所经过的时间。 优化传输性能时要考虑到:

  • 总传输量:所有需要传输的js文件的内容加起来,就是总传输量,重复代码越少,总传输量越小
  • 文件数量:当访问页面时,需要传输的js文件数量,文件越多,http请求越多,响应速度越慢
  • 浏览器缓存:js文件会被浏览器缓存,被缓存的js文件不会在进行传输 运行性能 运行性能是指,js代码在浏览器端的运行速度 它主要取决于我们如何书写高质量代码 永远不要过早的关注性能,因为你在开发的时候,无法完全预知最终的性能,过早的关注性能会极大的降低开发效率。

减少模块解析

什么是模块解析

alt text 模块解析包括:AST抽象语法树分析、依赖分析、模块语法替换

不做模块解析会怎么样?

如果对某个模块不做解析,该模块经过loader处理后的代码就是最终代码。 如果没有loader对该模块进行处理,该模块的源码就是最终的打包结果。 如果不对某个模块进行解析,可以缩短构建时间。

优化loader性能

进一步限制loader的应用范围

思路:对于某些库,不使用loader

例如:babel-loader可以转换ES6或更高级的语法,可有些库本身就是用ES语法书写的不需要转换,使用babel-loader反而会浪费构建时间。 lodash就是这样一个库。 lodash是在ES5之前出现的库,使用的是ES3的语法。 通过module.rule.excludemodule.rule.include,排除或仅包含需要应用loader的场景。

module.exports = {
  module:{
    rules:[
      {
        test:/\.js$/,
        use:['babel-loader'],
        exclude:/loadsh/,
        include:/jquery/
      }
    ]
  }
}

如果暴力一点,甚至可以直接排除掉node_modules目录中的模块,或者仅转换src目录的模块

module.exports = {
  module:{
    rules:[
      {
        test:/\.js$/,
        use:['babel-loader'],
        exclude:/node_modules/,
      }
    ]
  }
}

这种做法是对loader的范围进行进一步的限制,和noParse不冲突

缓存loader的结果

我们可以基于这样一种假设:如果某个文件内容不变,经过相同的loader解析后,解析后的结果也不变,于是我们可以将loader的解析结果保存下来,让后续的解析直接使用保存结果。

cache-loader可以实现这样的功能,

module.exports = {
  module:{
    rules:[
      {
        test:/\.js$/,
        use:['cache-loader', '...other loader'],
      }
    ]
  }
}

有趣的是,cache-loader明明放在最前面,为什么却可以决定后续loader是否运行 实际上loader的运行过程中还包含一个过程,就是pitch alt text 如图所示,pitch是一个函数,如果该loader由一个pitch方法,webpack就会在开始的时候首先执行各个loader的pitch,pitch会接收到一个文件路径,如果pitch没有返回,他就会接下去执行下一个loader的pitch,如果有返回,他就会将资源(也就是源码)传递给前一个loader,假设现在loader2的pitch有返回值,那么webpack就不会执行loader3.pitch、loader2、loader3,而是直接将资源传递给loader1,因此catch-loader放在第一位却可以控制后续的运行,一旦cache-loader发现存在缓存结果,它就直接返回,由于前面并没有其他loader,整个loader过程就直接结束,从而实现控制的效果;如果没有发现缓存值,就会执行下一个loader的pitch。 cache-loader可以实现个自定义的配置,详细间文档

为loader的运行开启多线程

thread-loader会开启一个线程池,线程池包含适量的线程 它会把后续的loader放到新的线程中运行,以提高构建效率 由于后续的loader会放到新线程中,所以后续的loader不能:

  • 无法使用webpack api生成文件
  • 无法使用自定义的plugin api
  • 无法访问webpack options

在实际开发中,可以进行测试,来决定thread-loader放在什么位置 特别注意:开启和管理线程需要消耗时间,小型项目中使用thread-loader反而会增加构建时间

热替换 HMR(Hot Module Replacement)

热替换并不能降低构建时间(可能还会稍微增加),但可以降低改动代码到效果呈现的时间。 当使用webpack-dev-server时,考虑代码改动到效果呈现的过程。 alt text 然而使用热替换之后,流程发生了变化。 alt text

使用和原理

  1. 更改配置
module.exports = {
  devServer:{
    hot:true //开启HMR
  },
  plugins:[
    new webpack.HotModuleReplacementPlugin()  
  ]
}
  1. 更改代码
// index.js
if(module.hot){ // 是否开启热更新
  module.hot.accept() // 接受热更新
}

首先这段代码是会参与运行的!! 当开启热更新后,webpack-dev-server会向打包结果中注入module.hot属性 默认情况下,webpack-dev-server不管是否开启热更新,当重新打包之后,都会调用location.reload刷新页面 但如果运行了module.hot.accept(),将改变这一行为 module.hot.accept()的作用是让webpack-dev-server通过socket管道,把服务器更新的内容发送到浏览器 然后将结果交给插件HotModuleReplacementPlugin注入的代码执行 插件HotModuleReplacementPlugin会覆盖原始代码,然后让代码重新执行 所以热替换发生在代码运行期间

样式热替换

对于样式也是可以使用热替换的,但需要使用style-loader 因为发生热替换的时候,HotModuleReplacementPlugin只会简单的运行模块代码 因此style-loader的代码一运行,就会重新设置style元素中的样式 而mini-css-extract-plugin,由于它生成文件是在构建期间,运行期间无法改动文件,因此他对于热替换是无效的

手动分包

基本原理

手动分包的总体思路是:

  1. 先单独打包公共模块 单独打包公共模块 公共模块会被打包成为动态链接库(dll Dynamic Link Library),并生成资源清单
  2. 根据入口模块进行正常打包 打包时,如果发现模块中使用了资源清单中描述的的模块,则不会形成下面的代码:
import $ from 'jquery'
import _ from 'lodash'
_.isArray($('.pich'))

由于资源清单中包含jquery和lodash两个模块,因此打包结果的大致格式是:

(function(module){
  ...
})({
  "./src/index.js":function(module, exports, __webpack_require__){
    var $ = __webpack_require__('./node_modules/jquery/index.js')
    var _ = __webpack_require__('./node_modules/lodash/index.js')
    _.isArray($('.pich'))
  },
  // 由于资源清单中存在,jquery的代码不会出现再这里
  "./node_modules/jquery/index.js":function(module, exports, __webpack_require__){
    module.exports = jquery
  },
  // 由于资源清单中存在,lodash的代码不会出现再这里
  "./node_modules/lodash/index.js":function(module, exports, __webpack_require__){
    module.exports = jquery
  }
})

打包公共模块

打包公共模块是一个独立的打包过程

  1. 单独打包公共模块,暴露变量名
// webpack.dll.config.js
module.exports = {
  mode:'production',
  entry:{
    jquery:['jquery'],
    lodash:"lodash"
  },
  output:{
    filename:'dll/[name].js',
    library:'[name]', // 暴露的全局变量
    libraryTarget:"var", // 暴露的方式
  }
}
  1. 利用DllPlugin生成资源清单
const webpack = require('webpack')
const path = require('path')
module.exports = {
  mode:'production',
  entry:{
    jquery:'jquery',
    lodash:"lodash"
  },
  output:{
    filename:'dll/[name].js',
    library:'[name]', // 暴露的全局变量
    libraryTarget:"var", // 暴露的方式
  },
  plugins:[
    new webpack.DllPlugin({
      path:path.resolve(__dirname,'dll','[name].mainfest.json'), // 资源清单保存位置
      name:'[name]' // 资源清单中暴露的变量名
    })
  ]
}

使用公共模块

  1. 在页面中手动引用公共模块
<script src='./dll/jquery.js'></script>
<script src='./dll/lodash.js'></script>
  1. 重新设置clean-webpack-plugin 如果使用clean-webpack-plugin,为了避免它把公共模块清除,需要做出以下配置
new CleanWebpackPlugin({
  // 要清除的文件或目录
  // 排除掉dll目录本身和它里面的文件
  cleanOnceBeforeBuildPatterns:['**/*',"!dll","!dll/*"]
})
  1. 使用DllReferencePlugin控制打包结果
module.exports = {
  plugins:[
    new webpack.DllReferencePlugin({
      mainfest:require('./dll/lodash.mainfest.json')
    }),
    new webpack.DllReferencePlugin({
      mainfest:require('./dll/lodash.mainfest.json')
    })  
  ]
}

总结

手动打包过程

  1. 开启output.library暴露公共模块
  2. 用DllPlugin创建资源清单
  3. 用DllReferencePlugin使用资源清单 手动打包注意事项
  4. 资源清单不参与运行,可以不放在打包目录中
  5. 记得手动引入公共js, 以及避免被删除
  6. 不要对小型公共js库使用 优点
  7. 极大提升自身模块打包速度
  8. 极大的缩小了自身体积
  9. 有利于浏览器缓存第三方库的公共代码 缺点
  10. 使用非常繁琐
  11. 如果第三方库中包含重复代码,则效果不太理想

自动分包

基本原理

不同于手动分包,自动分包是从实际的角度出发,从一个更加宏观的角度来控制分包,而一般不对具体那个包要分出去进行控制。 因此使用自动分包,不仅非常方便,而且更加贴合实际的开发需要。 要控制分包,关键是要配置一个合理的分包策略 有了分包策略之后,不需要额外安装任何插件,webpack会自动按照策略进行分包

实际上,webpack内部是使用splitChunkPlugin进行分包的 过去有一个库CommonsChunkPlugin也可以实现分包,不过由于该库某些地方并不完善,到了webpack4之后就被splitChunkPlugin取代 分包基本流程 从分包的流程至少可以看出一下几点:

  • 分包策略至关重要,它决定了如何分包
  • 分包时,webpack开启了一个新的chunk,对分离模块进行打包
  • 打包结果中,公共的部分被提取出来形成一个单独的文件,它是新chunk的产物

分包策略的基本配置

webpack提供optimization配置项,用于配置一些优化信息 其中splitChunks是分包策略的配置

module.exports = {
  optimization:{
    splitChunks:{
      // 分包策略
    }
  }
}

事实上,分包策略有其默认的配置,我们只需轻微的改动,即可应对大部分场景

  1. chunks 该配置项用于配置需要应用分包策略的chunk 我们知道分包是从已有的chunk中分离出新的chunk,那么哪些chunk需要分离呢? chunks有三个取值,分别是:
  • all:对于所有chunk都要应用分包策略
  • async:[默认]仅针对异步chunk应用分包策略
  • initial:仅针对普通chunk应用分包策略 所以你只需配置chunks为all即可
  1. maxSize 该配置可以控制包的最大字节数,如果某个包(包括分出来的包)超过了该值,则webpack会尽可能将其分离成多个包 但是不要忽略的是,分包的基础单位是模块,如果一个完整的模块超过该体积,它是无法做到再切割的,因此,尽管使用该配置,完全有可能某个包还是会超过该体积 另外,该配置看上去很美妙,实际意义上不大 因为分包的目的是提取大量的公共代码,从而减少总体积和充分利用浏览器缓存 虽然该配置可以把一些包进行再切分,但实际的总体积和传输量并没有发生变化

如果要进一步减少公共模块的体积,只能是压缩和tree shaking

分包策略的其他配置

如果不想使用其他配置的默认值,可以手动进行配置

  • automaticNameDelimiter: 新chunk名称的分隔符,默认~
  • minChunks:一个模块被多少个chunk使用时才会进行分包,默认是1
  • minSize:当分包达到多少字节后才被运行真正的拆分,默认值是30000

缓存组

之前配置的分包策略是全局的 而实际上,分包策略是基于缓存组的 每一套缓存组提供一套独有的策略,webpack按照缓存组的优先级依次处理每个缓存组,被缓存组处理过的分包不在需要再次分包 默认情况下,webpack提供两个缓存组:

module.exports = {
  optimization:{
    splitChunks:{
      chunks:'all',
      cacheGroups:{
        // 属性名是缓存组的名称,会影响到分包的chunk名
        // 属性值是缓存组的配置,缓存组继承所有的全局配置,也有自己的特殊配置
        vendors:{
          test:/[\\/]node_modules[\\/]/, // 匹配到相应模块时,这些模块进行单独打包
          priority: -10, // 缓存组优先级,优先级越高,该模块越先进行处理,默认值为0
        },
        default:{
          minChunks:1, // 覆盖全局配置,将最小chunk引用数改为2
          priority:-20, // 优先级
          reuseExistingChunk:true, // 重用已经被分离出去的chunk
        }
      }
    }
}

很多时候,缓存组对于我们来说没什么意义,因为默认缓存组就已经足够了 但我们同样可以利用缓存组完成一些事情,比如对公共样式的抽离

module.exports = {
  optimization:{
    splitChunks:{
      chunks:'all',
      cacheGroups:{
        styles:{
          test:/\.css$/
          minSize:0,
          minChunks:1,
        }
      }
    }
}

配合多页应用

虽然现在单页应用是主流,但免不了还是会遇到多页应用 由于多页应用中需要为每个html页面指定需要的chunk,这就造成了问题

new HTmlWebpackPlugin({
  template:"./public/index.html",
  chunks:['index-other','vendors~index-other','index']
})

我们必须手动指定被分离出去的chunk名称,这不是一种好办法, 幸好html-webpack-plugin的新版本解决了这个问题 做出如下配置即可:

new HTmlWebpackPlugin({
  template:"./public/index.html",
  chunks:['index']
})

它会自动的找到被index分离出去的chunk,并完成引用

原理

自动分包的原理其实并不复杂,主要经过以下步骤:

  1. 检查每个chunk编译的结果
  2. 根据分包策略,找到满足策略的模块
  3. 根据分包策略,生成新chunk打包这些模块(代码有所变化)
  4. 把打包出去的模块从原始包中剔除,并修正原始包代码 在代码层面有以下改动:
  5. 分包的代码中,加入一个全局变量,类型为数组,其中包含公共模块代码
  6. 原始包的代码中,使用数组中的公共模块

代码压缩

前言

  1. 为什么要进行代码压缩?
  • 减少代码体积
  • 破坏代码可读性
  • 提升破解成本
  1. 什么时候进行代码压缩 生产环境
  2. 使用什么压缩工具 目前最流行的代码压缩工具主要有两个:UglifyjsTerser Uglifyjs是一个传统的代码压缩工具,已存在多年,曾经是前端应用的必备工具,但由于它不支持es6语法,所以目前的流行度有所下降。 Terser是一个新起的代码压缩工具,支持ES6+语法,因此被很多构建工具内置使用,webpack安装后会内置Terser,当启用生产环境后即可用其进行代码压缩。

关于副作用 side function 副作用:函数运行过程中,可能会对外部环境造成影响的功能 如果函数包含以下代码,该函数称为副作用函数:

  • 异步函数
  • localStorage
  • 对外部数据进行修改 如果一个函数没有副作用,同时函数的返回结果仅依赖参数,则该函数称为纯函数(pure function)

webpack+Terser

webpack自动集成了Terser 如果你想更改、添加压缩工具,又或者想对Terser进行配置,使用下面的webpack配置即可

const TerserPlugin = require('terser-webpack-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
  optimization:{
    // 是否要启用压缩,默认情况下,生产环境会自动开启
    minimize:true,
    minimizer:[ // 压缩时使用的插件,可以配置多个
      new TerserPlugin(),
      new OptimizeCSSAssetsPlugin()
    ]
  }
}

tree shaking

代码压缩可以移除模块内部的无效代码 tree shaking 可以移除模块之间的无效代码

背景

某些模块导出的代码并不一定会被用到

// Math.js
export function sum(a,b)=>{
  return a+b
}
export function sub(a,b)=>{
  return a - b
}

// index.js
import { sub } from './Math.js'
console.log(sub(1,2))

上述模块之中并没有使用到sum函数,但是代码压缩并无法去除sum函数,导致sum成为无效代码 tree shaking就是用来解决上述的问题:移除模块之间的无效代码

使用

webpack2就开始支持了tree shaking 只要是生产环境,tree shaking就会自动开启

原理

webpack会从入口模块出发寻找依赖关系 当解析一个模块时,webpack会根据ES6的模块导入语句判断,该模块依赖了另一个模块的那个导出 webpack之所以选择ES6的模块导入语句,是因为ES6模块有以下特点:

  • 导入导出语句只能是顶层语句
  • import的模块名只能是字符串常量
  • import绑定的变量是不可变的 这些特征都非常有利于分析出稳定的依赖 在具体分析依赖时,webpack坚持的原则是:保证代码正常运行,然后再尽量tree shaking 所以如果你依赖的是一个导出的对象,由于js语言的动态特性,以及webpack的不够智能,为了保证代码正常运行,它不会移除对象中的任何信息 因此我们编写代码的时候,尽量:
  • 使用export xxx导出,而不使用 export default {xxx}导出
  • 使用import {xxx} from 'xxx'导入,而不是使用import xxx from 'xxx'导入 依赖分析完毕后,webpack会根据每个模块每个导出是否被使用,标记其他导出为dead code,然后交给代码压缩工具处理 代码压缩工具最终移除掉哪些dead code代码

使用第三方库

某些第三方库可能使用的是commonjs的方式导出,比如lodash,又或者没有提供普通的es6方式导出 对于这些库,tree shaking无法发挥作用 因此我们要寻找这些库的ex6版本,好在很多流行但没有使用的ex6的第三方库,都发布了ES6版本,比如lodash-es

作用域分析

tree shaking 本身并没有完善的作用域分析,可能导致一些再dead code函数中的依赖乃然会被视为依赖. 插件webpack-deep-scope-plugin提供作用域分析,可以解决该问题

副作用问题

webpack在tree shaking的使用,有一个原则:一定要保证代码的正确运行 在满足该原则的基础上,再来决定如何tree shaking 因此,当webpack 无法确定某个模块是否有副作用时,往往将其视为有副作用 因此,某些情况可能并不是我们想要的

// common.js
var n = Math.random()

// index.js
import './common.js'

虽然我们根本没有用到common.js的导出,但webpack担心common.js有副作用,如果去掉会影响某些功能 如果要解决该问题,就需要标记该文件是没有副作用的 在package.json中加入sideEffects

{
  ...
  "sideEffects":false
}

有两种配置:

  • false:当前工程中,所有模块都没有副作用。注意,这种写法会影响到某些css文件的导入
  • 数组:设置哪些文件拥有副作用,例如['!src/common.js'],表示只要不是src/common.js的文件,都有副作用

这种方式我们一般不处理,通常是一些第三方库在它们自己的package.json中标注

css tree shaking

webpack无法对css完成tree shaking,因为css跟es6没有半毛钱关系 因此对于css的tree shaking需要其他插件来完成 例如:purgecss-webpack-plugin

注意:purgecss-webpack-plugin对css module无能为力

ESLint

ESlint是一个针对js的代码风格检查工具,当不满足其要求的风格时,会给予警告或错误

使用

ESLint通常配合编辑器使用

  1. 在vscode中安装ESLint 该工具会自动检查工程中的js文件 检查的工作交给eslint库,如果当前工程没有就会去全局库中查找,如果都没有,则无法完成检查 另外,检查的依据是eslint配置文件.eslintrc,如果找不到工程的配置文件,也无法完成检查
  2. 安装eslint
npm i [-g] eslint
  1. 创建配置文件 可以通过eslint交互式命令创建配置文件
npx eslint --init

eslint会识别工程中的.eslintrc文件,也能够识别package.json中的eslintConfig字段

配置

env

  • browser: 代码是否在浏览器中运行
  • es6: 是否启用ES6全局API,例如Promise

parserOptions

该配置指定eslint对哪些语法的支持

  • ecmaVersion:支持ES语法版本
  • soucreType:
    • script:传统脚本
    • module:模块化脚本

parser

eslint的工作原理是先将代码进行解析,然后按照规则进行分析 eslint 默认使用Espree作为解析器,你可以在配置文件中指定一个不同的解析器

globals

配置可以使用的额外的全局变量

{
  "globals":{
    "var1":"readonly",
    "var2":"writable"
  }
}

eslint支持注释形式的配置,在代码中使用下面的代码注释也可完成配置

/* global var1,var2 */
/* global var3:readonly, var4:writabal */

extends

配置继承自哪里

ignoredFile

排除掉不需要验证的文件 .eslintignore

rules

eslint规则集 每条规则影响某个方面的代码风格 每条规则都有下面几个取值:

  • off或0或false:关闭该规则检查
  • warn或1或true:警告,不会导致程序退出
  • error或2:错误,当被触发的时候,程序会退出 除了配置文件中的规则,还可以在注释中使用
/* eslint eqeqes:0 , curly: 0  */

bundle analyzer

对每个模块具体的体积进行分析 alt text 安装一个插件即可

npm i -D webpack-bundle-analyzer

使用

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
module.exports = {
  plugins:{
    new BundleAnalyzerPlugin()
  }
}