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 序列帧动画 渲染器
相关文章
|
缓存 Rust 前端开发
60行代码实现一个基于esbuild按需加载的loader(一)
大家好,我是Fly, 最近研究的技术就是esbuild,一个新技术的 出现必然是解决是某些问题的, 这不我就打算用它来进行项目的编译速度进行操刀了, 前端对于tsx 的编译方式 无非是两种,
60行代码实现一个基于esbuild按需加载的loader(一)
|
前端开发 JavaScript Java
前端——使用RequireJS的r.js打包压缩模块
前端——使用RequireJS的r.js打包压缩模块
|
JavaScript CDN
vue_按需引入elment、echarts和路由懒加载,减少打包体积
vue_按需引入elment、echarts和路由懒加载,减少打包体积
230 0
|
Web App开发 前端开发 JavaScript
UMD 被淘汰了吗?不考虑的 UMD 的库如何在纯 UMD 前端项目中运行?
UMD 被淘汰了吗?不考虑的 UMD 的库如何在纯 UMD 前端项目中运行?
258 0
|
1月前
|
中间件 API
Next.js 实战 (八):使用 Lodash 打包构建产生的“坑”?
这篇文章介绍了作者在使用Nextjs15进行项目开发时遇到的部署问题。在部署过程中,作者遇到了打包构建时的一系列报错,报错内容涉及动态代码评估在Edge运行时不被允许等问题。经过一天的尝试和调整,作者最终删除了lodash-es库,并将radash的部分源码复制到本地,解决了打包报错的问题。文章最后提供了项目的线上预览地址,并欢迎读者留言讨论更好的解决方案。
41 10
|
9月前
|
JavaScript
【第12期】Vue3+TypeScript+Vite中动态引入图片等静态资源
【第12期】Vue3+TypeScript+Vite中动态引入图片等静态资源 c
1395 0
|
JavaScript 存储
requirejs 多页面,多js 打包代码,requirejs多对多打包【收藏】
这段代码来自 http://stackoverflow.com/questions/20583812/grunt-requirejs-optimizer-for-a-multi-app-project 以前用 requirejs和r.js  也做过打包demo, demo 大家懂得,1个页面10多个js。
967 0
|
JavaScript 索引 开发者
Lodash 扩展JS通用方法
版权声明:本文首发 http://asing1elife.com ,转载请注明出处。 https://blog.csdn.net/asing1elife/article/details/82848345 Lodash是一个著名的javascript原生库,不需要引入其他第三方依赖。
1797 0
|
7月前
|
JavaScript 前端开发
前端 JS 经典:Vite 打包优化
前端 JS 经典:Vite 打包优化
316 0
|
9月前
|
前端开发 JavaScript Shell
stylus详解与引入
stylus详解与引入

热门文章

最新文章