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

vite-plugin-demand-import

v1.1.1

Published

Demand import for the library that has side effects

Downloads

171

Readme

Release npm version

📙English Document

Vite Plugin Demand Import

带有副作用 的库提供 “按需加载” 功能。

快速开始

pnpm add vite-plugin-demand-import -D
import { defineConfig } from 'vite'
import demandImport from 'vite-plugin-demand-import'

export default defineConfig({
  plugins: [
    demandImport({
      lib: 'antd-mobile',
      resolver: {
        js({ name }) {
          return `antd-mobile/es/components/${name}`
        }
      }
    })
  ]
})

/////////// 编译结果 ////////////
import { Button } from 'antd-mobile'

↓ ↓ ↓ ↓ ↓ ↓

import Button from 'antd-mobile/es/components/button'

优化效果

before.png


after.png

接口

export type ResolverOptions = {
  /**
   * 被导入的模块标识,import { Button } from 'antd-mobile' 中 name 等于 'Button'
   */
  name: string

  /**
   * 当前被解析文件的 id,一般是文件的绝对路径
   */
  file: string
}

/**
 * 返回 import xxx from 'yyy' 语句中的 yyy
 */
export type Resolver = (options: ResolverOptions) => string

export type DemandImportOptions = {
  /**
   * 类库的名称,用来判断当前 import 语句是否需要处理
   */
  lib: string

  /**
   * 库的命名风格
   *
   * @default  "kebab-case"
   * @description "default" 将不做处理
   */
  namingStyle?: 'kebab-case' | 'camelCase' | 'PascalCase' | 'default'

  /**
   * 路径解析器
   */
  resolver: {
    js?: Resolver // 返回 js 文件的导入路径
    style?: Resolver // 返回样式文件的导入路径
  }
}

Why

在使用各种 “xxx-import” 插件之前我们需要先区分几个概念:

  1. tree shaking:中文 “摇树”,指对没有使用到的代码在 编译阶段 进行删减。
  2. 自动导入:根据配置的策略在 编译阶段 自动插入导入语句。
  3. 按需加载:指在 运行时 动态返回用户当前访问的资源。

简单的代码说明:

  1. tree shaking:
// a.js
export const a1 = 1
export const a2 = 2

//b.js
export const b1 = 1
export const b2 = 2

// index.js
import { a1 } from './a'
import { b1 } from './b'

console.log(a1)

////////// 使用 rollup 打包 ////////////

// dist/index.js
const a1 = 1
console.log(a1)

a.js & b.js 中没有用到的代码都被删减掉了。

  1. 自动导入:
import { Button } from 'antd'

///////// 使用 xxx-import 插件 /////////
import { Button } from 'antd'
// 自动插入了样式的导入语句
import 'antd/lib/style/button/index.less'
  1. 按需加载:
if (location.pathname.include('/login')) {
  import('./login.js').then((res) => {
    // do something
  })
}

目前主要是路由中用的多,比如 vue-routerreact-router 中我们经常会配置:

export const routes = [
  {
    name: 'login',
    path: '/login',
    component: () => import('./Login/index')
  }
]

需要注意的是 日常表述中 “按需加载” 在不同的场景下跟 “tree shaking” 或者 “自动导入” 是等价的。比如:

  1. 我们使用 lodash 库时期望的是 “tree shaking” 行为,但是我们也可以说成 “按需加载” lodash。
  2. 我们在使用 UI 库时期望的是 “自动导入” 行为,但是我们也可以说成 “按需加载” 样式。

tree shaking

网上关于这个特性的文章很多了,这里只大概说一下我知道的几种方式:

  1. esm 规范的 export & import 是静态绑定的,rollup 编译时可以分析出哪个模块是用到的哪个模块是没用到的,只要类库的 package.json 申明了 sideEffects: false 便可以自动 tree shaking。
  2. commonjs 规范的 require 是动态的,所以 tree shaking 是非常困难的。rollup 处理 commonjs 模块时依赖 @rollup/plugin-commonjs 插件进行 commonjs 到 esm 的转换。这个插件默认的行为是:如果模块使用 exports.xxx 导出会 tree shaking,而 modules.exports 导出则不会。
  3. 手动指定包下面的具体模块,比如使用 import round from 'lodash/round' 代替 import { round } from 'lodash'

关于这块可以使用 lodashlodash-es 进行测试,前者不会 tree shaking 而后者会。

静态&动态导入的简单解释

一句话概括就是可以在运行时按需导入的就是“动态导入”,在编码时写死的就是“静态导入”:

import a from 'a' // 静态导入

if (flag) {
  import 'a' // 动态导入,esm 不支持该行为
}
const a = require('a') // 静态导入

if (flag) {
  require('a') // 动态导入,commonjs 支持该行为
}

目前已有的 xxx-import 插件

babel-plugin-import

antd 官方出品,感觉可以算这个领域最知名的类库吧,功能非常齐全。比如:

  1. tree shaking: 因为当时还是 webpack 的天下,大多数的包也都是 commonjs 规范的,所以当时一般都是使用第三种方式来实现该特性。对应的配置为:
// .babelrc
{
  "plugins": [["import", {
      "libraryName": "antd"
  }]]
}

// 效果
import { Button } from 'antd';

      ↓ ↓ ↓ ↓ ↓ ↓

var _button = require('antd/lib/button');
  1. 自动导入:使用 UI 库的时候一般可以选择 “全局导入” 和 “按需导入” 引入样式文件,当选择后者的时候就可以让插件帮我们自动插入导入语句:
// .babelrc
{
  "plugins": [["import", {
      "libraryName": "antd",
      style: true
  }]]
}

// 效果
import { Button } from 'antd';


      ↓ ↓ ↓ ↓ ↓ ↓

var _button = require('antd/lib/button');
require('antd/lib/button/style'); // 自动插入了导入语句

这里只展示最简单的用法,更多复杂的配置可以查看官方文档,感觉基本没有解决不了的场景。

不过随着 esm 的普及以及在常规项目中 “全局导入” 比 “按需加载” 并不会大多少体积,所以作者推荐? 用 “全局导入” 代替 “按需加载”,这个插件也就不再需要了。

相关结论是 babel-plugin-import 某个 issue 中作者说的,一时找不到链接了 ~~~

vite-plugin-style-import

库如其名,这个是 vite 框架的插件。

因为 vite 是基于 rollup 构建的,js 代码一般都可以自动 tree shaking,所以只需要处理样式文件的 “自动导入” 就好了:

import { ElButton } from 'element-plus';

        ↓ ↓ ↓ ↓ ↓ ↓

// dev
import { Button } from 'element-plus';
import 'element-plus/lib/theme-chalk/el-button.css`;

// prod
import Button from 'element-plus/lib/el-button';
import 'element-plus/lib/theme-chalk/el-button.css';

值得一提的是作者非常细心的区分了开发跟正式环境:

  1. 开发环境中 vite 的 optimize 不会因为新的组件导入而刷新。
  2. 可能是怕有些类库没有配置 sideEffects 导致 tree shaking 不生效?

我遇到的问题

上面扯了那么多终于到重点了,哈哈。

其实问题还是出在 UI 库构建时对样式的处理差异上,比如 antd 的组件代码跟样式是分离的:

button
    |—— style/index.js
    └—— index.js

这时候使用 vite-plugin-style-import 自动导入样式就好了。

但是 antd-mobile 构建出来的是:

button
    |—— button.css
    |—— button.js
    └—— index.js
// index.js
import './button.css'
import { Button } from './button'
export default Button

官方为了“方便”用户使用而自动在组件入口引入了样式文件,对应的 sideEffects 配置:

  "sideEffects": [
    "**/*.css",
    "**/*.less",
    "./es/index.js",
    "./src/index.ts",
    "./es/global/index.js",
    "./src/global/index.ts"
  ],

当我们在 vite 中使用时:

import { Button } from 'antd-mobile'

第一步,rollup 会去 node_module/antd-mobile/package.json 中查看 module 或者 main 字段定义的入口文件。

第二步,找到 node_module/antd-mobile/es/index.js 文件:

第三步,处理 index.js 文件中的导入。因为 button 被用到了肯定会加载,而其他组件因为没有用到会被 tree shaking 掉。但是这里存在一个问题:每个组件都引入了样式文件,而 css 类型是被定义成 “有副作用” 的(这个没错)。这就导致组件的 js 文件虽然不会导入,但这个组件所引用的样式会被导入,最后就是整个库的 css 全部被导入了。

众所皆知,vite 的性能由传统的 bundle 处理能力转向了浏览器处理请求的效率:

  1. 提高浏览器的并发量:开启 vite 的 https 服务。
  2. 浏览器缓存:因为 vite 内置的 https 是自签名证书通不过浏览器检测,本地缓存是不会生效的。这个时候可以使用我的 vite-plugin-mkcert 插件为 https 提供本地证书支持。
  3. 减少请求量。

回到这个问题,解决方式有两个:

  1. 向官方反馈把组件中的样式引入去掉,这不现实(我个人感觉这是个“反优化”)。
  2. 不要从包的入口去引用组件,改成指定模块的导入:
import { Button } from 'antd-mobile'

↓ ↓ ↓ ↓ ↓ ↓

import Button from 'antd-mobile/es/components/button'

emm,兜兜转转终于还是回到了原点,就问苍天饶过谁 😘


所以这个问题是可以用 babel-plugin-import 来解决的,而 vite 中也有 vite-plugin-importer 对应封装的插件。

但是我觉得在 rollup 中使用 babel 很不 “vite”,哈哈哈 ~~~