60行代码实现一个基于esbuild按需加载的loader(二)

简介: 设计整个插件的构建思路 就是 读取配置, 然后判断是否需要 按需加载, 如果需要 就利用的 babel 能力, 去做转化, 转换完成用 esbuild 去做编译就好了。

设计



整个插件的构建思路 就是 读取配置, 然后判断是否需要 按需加载, 如果需要 就利用的 babel 能力, 去做转化, 转换完成用 esbuild 去做编译就好了。



image.png



项目源码 都在这里, 如果你loader 都不是特别会的, 建议学习下loader, 然后再去看这边文章


按需加载


import { Button } from 'antd';
// 👇🏻 👇🏻 👇🏻 👇🏻 👇🏻 👇🏻 👇🏻 👇🏻 👇🏻 👇🏻 //
import 'antd/lib/button/style/css';
import Button from 'antd/lib/button';


按需加载就是 比如 我项目 中引用到了 「antd」 的组件库, 但是我打包的时候, 不可能把antd 的所有组件库都打包进去的, 不利于 treeShaking, 所以我们就自己手动 转成 下面这种格式, 引用到 具体文件, 但是 有同学就会说, 我如果自己手动改, 那也可以哇, 但是对于一些大型项目, 你一个个改, 不累嘛, 然后再团队合作过程中,你能确保每一个同学有这种意识嘛, 每个人的理解都不一样, 所以借助工程化的能力,  统一操作就OK了。这就是工程化的意义, 定义统一的规范, 然后几乎看起来每个人写的代码都是一样的。


loader 的转化本质是传入字符串 , 在吐出 字符串的过程, 方便 下一个loader 继续操作, 所以 loader  一般都是 倒序执行。


比如是 loader 传入的是下面这些 字符串


import React, { useEffect, useState } from 'react'
import { Button } from 'antd'
function Home() {
  return <div>我是home 页</div>
}


我们要将其进行改变要进行 下面几个步骤, 第一步


「借助 babel 的 接口 生成 AST 节点」


import parser from "@babel/parser";
const ast = parser.parse(source, { sourceType: 'module', plugins: ['jsx', 'typescript'] })


第二步 traverse 遍历 ast 节点 的   import 部分


import traverse from '@babel/traverse'
traverse(res, {
  ImportDeclaration: function (path) {
     console.error(path.node, '/n')
     console.error(path.node.specifiers)
  },
})


我们看下遍历的结果


image.png


我们核心就是 拿到当前 引用的组件名字  就是 「Button」

所以就是替换当前的 同时增加 一条 css 的 引入 节点


let node = path.node
    let specifiers = node.specifiers
    const { library, customName, customStyle } = opts
    if (library == node.source.value && !types.isImportDeclaration(specifiers[0])) {
      let newImport: any[] = []
      specifiers.forEach((specifier) => {
        newImport.push(
          types.importDeclaration(
            [types.importDefaultSpecifier(specifier.local as types.Identifier)],
            types.stringLiteral(customName(specifier.local.name)),
          ),
        )
        // 增加一条新的节点
        addSideEffect(path, `${customStyle(specifier.local.name)}`)
      })
      // 替换当前 节点
      path.replaceWithMultiple(newImport)
    }

这里的 我们将节点的名字 回调出来, 由外层决定,怎么定义 引用路径 类似于这样 每一个库的默认文件可能不一样

const opts = {
  library: 'antd',
  customName: (name: string) => {
    return `antd/lib/${name}`
  },
  customStyle: (name: string) => {
    return `antd/lib/${name}/style`
  },
}


编译


编译解决完毕,  然后利用 esbuild 的 transform  去编译对应的 source


// 1. 获取异步回调函数
  this.cacheable && this.cacheable()
  const callback = this.async()
 const { code, map } = await transform(source, transformOptions)
 callback(null, code, map && JSON.parse(map))


loader 的接口类型, 和 esbuild 保持一致, 只是踢出了 sourcemap 和 sourcefile 两个 属性  同时保持了。加了一些默认值。整体的流程到这里就结束了, 其实整体来说还是比较简单的。


构建效率



我在大型react 进行了试验, 看看这东西 到底能优化多少, 没使用 esbuild-import-loader  去打包 整体的 速度。大概模块在800 个


image.png

image.png


速度 大概快了2 倍就光是  ts 和 tsx  层面的打包,所以说整体的构建效率 还是 比较明显的 。说一下遇到的坑吧, esbuild 作为新兴的前端构建工具, 就是 esbuild 的  tsx  loader   用来 编译  ts  某些语法 文件 会报错。比如:


const requestInstance = createFetch(remote)
  return <T>({ path, params, method, headers, ...config }: RequestConfig) =>
    requestInstance<ResponseBody<T>>(path, { params, method, headers, ...config })
}


这也是最常见的问题 比如 无法在tsx加载器中对箭头函数表达式使用泛型类型参数,如「<T>(=>{})」 ,  tsx加载器不是ts加载器的超集。它们是两种不同的部分不兼容的语法。例如,字符序列


<a>1</a>/g


使用ts加载器解析为


<a>(1 < (/a>/g))


,使用tsx加载器则解析为


(<a>1</a>) / g


解决方案 就是 tsx  和 ts  匹配分开来 如下面 这样配置, 这样就可以解决了。


{
    test: /\.tsx/,
    use: [
     {
        loader: 'esbuild-import-loader',
        options: {
          loader: 'tsx',
          target: 'es2015',
          libraryName: 'xxx-desigin',
          customName: (name: string) => {
            return `xx-desigin/es/${name}/index.js`
          },
          customStyle: (name: string) => {
            return `xx-desigin/es/${name}/style/css.js`
          },
        },
      },
    ],
    exclude: /node_modules/,
  },
   {
    test: /\.ts/,
    use: [
     {
        loader: 'esbuild-import-loader',
        options: {
          loader: 'ts',
          target: 'es2015',
          libraryName: 'cat-desigin',
          customName: (name: string) => {
            return `xxx-desigin/es/${name}/index.js`
          },
          customStyle: (name: string) => {
            return `xxx-desigin/es/${name}/style/css.js`
          },
        },
      },
    ],
    exclude: /node_modules/,
  },


总结



目前代码已经开源到 这个   github  上, 同时 也发了 npm 包  github 地址 如下:


image.png


感兴趣的小伙伴, 可以 试一试 玩一玩, 欢迎👏🏻提bug 给我 职业 改bug 哈哈哈。后面还会分享的内容如下


  1. 如何打包一个 合格的 npm 包


  1. 基于storybook 的组件库 技术搭建


  1. vite  rollup webpack 打包之间的差异


  1. 基于canvas 的 react 序列帧动画 渲染器
相关文章
|
2月前
|
JavaScript 前端开发
前端 JS 经典:Vite 打包优化
前端 JS 经典:Vite 打包优化
155 0
|
2月前
|
前端开发 JavaScript
前端 JS 经典:Vite 分包配置
前端 JS 经典:Vite 分包配置
74 0
|
4月前
|
前端开发 JavaScript Shell
stylus详解与引入
stylus详解与引入
|
前端开发 JavaScript Java
前端——使用RequireJS的r.js打包压缩模块
前端——使用RequireJS的r.js打包压缩模块
|
自然语言处理 前端开发 JavaScript
Babel 的工作原理以及怎么写一个 Babel 插件
Babel 的工作原理以及怎么写一个 Babel 插件
180 0
|
JavaScript 前端开发
Vue使用PostCSS怎样使用sass
众所周知转换 px 单位的插件有很多,知名的有 postcss-px-to-viewport 和 postcss-pxtorem,前者是将 px 转成 vw,后者是将 px 转成 rem,精简了不常用的配置。将成为vw优先单位使用,以rem作为回退模式。考虑到vw在移动设备的支持度不如rem,这款插件很好的解决了该问题。然后简单的介绍下。
98 0
|
Web App开发 前端开发 JavaScript
UMD 被淘汰了吗?不考虑的 UMD 的库如何在纯 UMD 前端项目中运行?
UMD 被淘汰了吗?不考虑的 UMD 的库如何在纯 UMD 前端项目中运行?
206 0
|
缓存 Rust 前端开发
esbuild + swc 能有多快?
前端工具层出不穷,之前有常用的打包工具webpack,现在有了速度更快的vite。 vite的开发模式是基于esBuild编译的,打包又是基于rollup,启动项目是很快的。
765 0
esbuild + swc 能有多快?
|
缓存 Rust 前端开发
60行代码实现一个基于esbuild按需加载的loader(一)
大家好,我是Fly, 最近研究的技术就是esbuild,一个新技术的 出现必然是解决是某些问题的, 这不我就打算用它来进行项目的编译速度进行操刀了, 前端对于tsx 的编译方式 无非是两种,
60行代码实现一个基于esbuild按需加载的loader(一)