IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    使用Rollup构建npm库

    神奇的程序员发表于 2023-03-19 15:07:37
    love 0

    前言

    前几天在调试截图插件的时候,想使用yarn link将本地打包好的文件软链接到项目里,以便于快速定位问题时,出现了错误,经过一番折腾后发现锅在Vue CLI这里。

    本文就跟大家分享下我从Vue CLI迁移至Rollup的整个过程,欢迎各位感兴趣的开发者阅读本文。

    为什么要更换架构

    使用link语法链接本地构建好的库,需要提供esm格式的文件,Vue CLI的打包配置选项中并没有提供这个选项。可能很多开发者跟我一样有个疑惑:既然包里没提供此格式的文件,为什么使用yarn add一个包的时候就可以正常运行呢?我带着这个疑问查了下,发现在添加包的过程中,它会自己构建esm版本的出来。

    排查到问题后,我想着可能我用的构建工具版本太老了,于是我就翻了下Vue CLI的官网,依然没找到相关的配置选项,在官网还看到了 Vue CLI is in Maintenance Mode!的警告。

    image-20230315213947278

    Vue CLI已经进入维护模式了(webpack成了旧爱),并且在极力推荐Vite(rollup成了新欢)。一个时代结束了,一开始本来还寻思着用vite呢,但是想了下用别人封装好的东西局限性还是太大了,况且它的大部分功能我是用不到的。因此,我最终决定了使用Rollup来作为项目的架构。

    可能很多开发者不太了解我上面所说的“新欢旧爱”,我在这里做个解释吧:

    • Vue CLI默认使用webpack作为打包工具
    • Vite使用Rollup来处理库的打包,使用ESBuild作为默认的构建工具

    预期效果

    我们先来看下Vue CLI都帮我们处理了哪些事情吧:

    • 处理scss文件
    • 处理ts文件
    • 处理静态资源文件
    • 处理vue文件
    • 处理路径别名
    • 构建umd、cjs格式的文件

    舍弃它,拥抱rollup生态,我们需要将这些一一实现。

    如果你对我的截图插件实现原理比较感兴趣,请移步:实现Web端自定义截屏

    环境搭建

    看了一圈Rollup官方文档对它有了一个基本的了解,我们将项目里有关Vue CLI的依赖包以及配置文件删掉后,就可以安装rollup的基本依赖包了(此处我们使用的rollup版本为2.x,依赖的node版本为14.18.0)。

    yarn add rollup@^2.59.2 -D

    随后,我们在项目的根目录创建rollup.config.js文件,添加如下所示的配置代码:

    • input 项目的入口文件
    • output 打包配置
      • file 目标文件路径
      • format 打包格式
      • name 对外暴露的模块名
    export default {  input: "src/main.ts",  output: [    {      file: "dist/screenShotPlugin.umd.js",      format: "umd",      name: "screenShotPlugin"    },    {      file: "dist/screenShotPlugin.esm.js",      format: "es",      name: "screenShotPlugin"    },    {      file: "dist/screenShotPlugin.common.js",      format: "cjs",      name: "screenShotPlugin"    }  ]}

    最后,在package.json中添加执行脚本。

    {  "scripts": {    "build-rollup": "rollup -c"  }}

    执行yarn run build-rollup后,我们发现报错[!] Error: Unexpected token (Note that you need plugins to import files that are not JavaScript),它提醒我们需要插件才能处理非JavaScript文件。

    image-20230316071911014

    根据官方文档所述,rollup提供了plugins选项供使用者对其进行扩展,使用也非常简单。

    • 安装依赖包
    • 修改配置文件

    此处,我们要处理ts文件,打包cjs格式的文件,需要用到

    • rollup-plugin-typescript2 用于处理.ts类型的文件
    • @rollup/plugin-commonjs 用于将 CommonJS 模块转换为 ES6 模块
    • @rollup/plugin-node-resolve 用于将模块解析为 ES6 模块的插件,它可以帮助 Rollup 在打包时正确地解析模块的导入路径,使得打包后的代码可以在浏览器中运行。通常会将此插件放到其他插件的前面。
    yarn add rollup-plugin-typescript2 @rollup/plugin-commonjs @rollup/plugin-node-resolve -D

    修改rollup.config.js文件

    import typescript from "rollup-plugin-typescript2";import { nodeResolve } from "@rollup/plugin-node-resolve";import commonjs from "@rollup/plugin-commonjs";export default {  input: "src/main.ts",  plugins: [    nodeResolve(),    commonjs(),        typescript({      tsconfig: "tsconfig.json",      tsconfigOverride: {        compilerOptions: {          declaration: true        }      },      clean: true    })  ]  }

    再次执行构建命令后,插件成功打包,根目录出现dist文件夹。

    image-20230316214005952

    这就是rollup最基本的用法。如果你的项目很简单,并且只使用了ts。那么做到这些已经完全符合你的需求了。

    强大的插件系统

    如前所述,为了实现预期效果,我们需要使用Rollup的插件系统。在本章节中,我将跟大家分享达到预期效果所需的插件。

    路径别名

    我的项目中使用@来代表"src"目录,rollup在打包的时候默认是识别不了的,我们需要通过@rollup/plugin-alias插件来处理。

    yarn add @rollup/plugin-alias -D

    rollup.config.js

    import alias from "@rollup/plugin-alias";export default {  // ...其他配置省略  plugins: [    alias({      entries: [{ find: "@", replacement: path.resolve(__dirname, "src") }]    })  ]  }

    处理CSS文件

    我项目中使用的CSS预处理器是scss,在rollup中处理scss需要使用rollup-plugin-postcss插件,还需要其他一些插件作为辅助。

    • postcss CSS 预处理器,它可以用 JavaScript 编写插件来对 CSS 进行转换和优化。

    • postcss-preset-env PostCSS 的一个预设,它包含了一组常用的 PostCSS 插件,可以帮助我们自动转换最新的 CSS 语法和特性,以兼容目标浏览器。

    • postcss-import 处理css文件中的@import语句

    • postcss-url 处理css中引入的静态资源

    • @rollup/plugin-url 处理静态资源,通过配置阀值来决定文件是否转base64还是拷贝文件

    • cssnano 移除注释、空格和其他不必要的字符来压缩CSS代码

    • autoprefixer 给css3的一些属性加前缀,兼容其他内核的浏览器

    yarn add postcss postcss-preset-env postcss-import postcss-url @rollup/plugin-url cssnano autoprefixer -D

    rollup.config.js

    import postcss from "rollup-plugin-postcss";import autoprefixer from "autoprefixer";import postcssImport from "postcss-import";import postcssUrl from "postcss-url";import url from "@rollup/plugin-url";import cssnano from "cssnano";export default {  // ...其他配置省略  plugins: [    postcss({      // 内联css      extract: splitCss === "true" ? "style/css/screen-shot.css" : false,      minimize: true,      sourceMap: false,      extensions: [".css", ".scss"],      // 当前正在处理的CSS文件的路径, postcssUrl在拷贝资源时需要根据它来定位目标文件      to: path.resolve(__dirname, "dist/assets/*"),      use: ["sass"],      // autoprefixer: 给css3的一些属性加前缀      // postcssImport: 处理css文件中的@import语句      // cssnano: 它可以通过移除注释、空格和其他不必要的字符来压缩CSS代码      plugins: [        autoprefixer(),        postcssImport(),        // 对scss中的别名进行统一替换处理        postcssUrl([          {            filter: "**/*.*",            url(asset) {              return asset.url.replace(/~@/g, ".");            }          }        ]),        // 再次调用将css中引入的图片按照规则进行处理        postcssUrl([          {            basePath: path.resolve(__dirname, "src"),            url: "inline",            maxSize: 8, // 最大文件大小(单位为KB),超过该大小的文件将不会被编码为base64            fallback: "copy", // 如果文件大小超过最大大小,则使用copy选项复制文件            useHash: true, // 进行hash命名            encodeType: "base64" // 指定编码类型为base64          }        ]),        cssnano({          preset: "default" // 使用默认配置        })      ]    }),      // 处理通过img标签引入的图片    url({      include: ["**/*.jpg", "**/*.png", "**/*.svg"],      // 输出路径      dest: "dist/assets",      // 超过10kb则拷贝否则转base64      limit: 10 * 1024 // 10KB    })  ]  }

    如果你使用的是less或者Stylus,只需要修改上方配置中的extensions选型,将.scss换成你所使用的预处理语言即可。

    JS代码转换

    相信大家对babel都不陌生,它能够将ES6+代码转换为可以在浏览器中运行的ES5代码,能够最大程度的兼容低版本浏览器。在rollup中我们需要使用@rollup/plugin-babel插件实现这个转换。

    yarn add @rollup/plugin-babel -D

    rollup.config.js

    import babel from "@rollup/plugin-babel";export default {   // ...其他配置省略   plugins: [    nodeResolve({      // 读取.browserslist文件      browser: true,      preferBuiltins: false    }),    typescript({      tsconfig: "tsconfig.json",      tsconfigOverride: {        compilerOptions: {          // 指定目标环境为es5          target: "es5"        }      },      clean: true    }),    babel()   ]  }

    除此之外,还需要在根目录创建babel的配置文件babel.config.js。我们还需要额外安装:

    • @babel/core Babel 的核心库,提供了 Babel 的编译器、转换器等基础功能。
    • @babel/preset-env Babel 的一个预设,包含了一系列转换规则和插件的预设,能够根据指定的目标环境,自动转换最新的 JavaScript 语法和 API,以兼容目标环境。
    yarn add @babel/core @babel/preset-env -D

    babel.config.js

    module.exports = {  presets: ["@babel/preset-env",{ targets: "defaults" }],  plugins: []};

    处理静态资源

    在项目开发过程中,会有一些文件不需要通过打包工具来处理(字体文件、html文件等),我们希望将它原封不动的复制到目标路径下。在rollup中我们需要使用rollup-plugin-copy插件来实现。

    yarn add rollup-plugin-copy -D

    rollup.config.js

    import copy from "rollup-plugin-copy";export default {  // ...其他配置省略  plugins: [    copy({      targets: [        {          src: "src/assets/fonts/**",          dest: "dist/assets/fonts"        },        {          src: "public/**",          dest: "dist"        }              ]    })  ]  }

    处理Vue文件

    我的截图插件还有一个vue3版本的,需要用rollup进行处理的话,需要使用6.x版本的rollup-plugin-vue插件。

    yarn add rollup-plugin-vue@^6.0.0 -D

    rollup.config.js文件

    import vue from "rollup-plugin-vue";export default {  // ...其他配置省略  output: [    {      file: "dist/screenShotPlugin.umd.js",      format: "umd",      name: "screenShotPlugin",      globals: {        vue: "Vue"      }          },    {      file: "dist/screenShotPlugin.esm.js",      format: "es",      name: "screenShotPlugin",      globals: {        vue: "Vue"      }          },    {      file: "dist/screenShotPlugin.common.js",      format: "cjs",      name: "screenShotPlugin",      globals: {        vue: "Vue"      }          }  ],    external: ["vue"],  plugins: [    vue({      target: "browser",      css: true,      // 把组件转换成 render 函数      compileTemplate: true,      preprocessStyles: true,      preprocessOptions: {        scss: {          includePaths: ["src/assets/scss"]        }      }    }),      ]}

    注意⚠️:如果你用的是vue2.x则需要安装5.x版本的rollup-plugin-vue插件,并且他们的配置也有所差异,具体的用法请参考该插件的官方文档。

    清理目标文件

    打包项目到目标路径时,大多数情况下,我们都需要先将目标目录进行清空。要实现这个功能,就需要用到rollup-plugin-delete插件。

    import delFile from "rollup-plugin-delete";export default {  // ...其他配置省略  plugins: [    delFile({ targets: "dist/*" })  ]  }

    模块占用分析

    在一些情况下,我们可能需要了解打包后的文件各个模块的空间占用情况,便于进一步的优化。要实现这个功能,需要用rollup-plugin-visualizer插件。

    yarn add rollup-plugin-visualizer -D

    rollup.config.js

    import visualizer from "rollup-plugin-visualizer";export default {  // ...其他配置省略  plugins: [    visualizer({      filename: "dist/bundle-stats.html"    })  ]  }

    构建完成后,dist文件夹下的bundle-stats.html文件就展示了各个模块的空间占用情况。

    image-20230318103746744

    代码压缩

    当我们要把打包后的文件部署到生产环境时,通过对打包后的文件进行压缩可以节省网络的开销,提升网站的加载速度。要实现这个功能,就需要用到rollup-plugin-terser插件。

    yarn add rollup-plugin-terser -D

    rollup.config.js

    • 在output配置中的输出配置对象中添加plugins属性
    • 如果你想为所有的output都添加代码压缩,那么你可以直接添加近plugins内
    import { terser } from "rollup-plugin-terser";export default {  // ...其他配置省略  output: [    {      file: "dist/screenShotPlugin.umd.js",      format: "umd",      name: "screenShotPlugin",      plugins: [        terser()      ]        }  ]  //plugins: [  //  terser()  //]    }

    打包进度条

    在打包的时候,我们希望知道打包的进度,此时就需要用到rollup-plugin-progress库来实现。

    yarn add rollup-plugin-progress -D

    rollup.config.js

    import progress from "rollup-plugin-progress";export default {  // ...其他配置省略  plugins: [    progress({      format: "[:bar] :percent (:current/:total)"    })  ]  }

    设置开发服务器

    我们在开发库的时候,为了调试方便,希望改了后就能很快看到效果,而不是每次都打包才行。要实现这个功能我们需要用到

    • rollup-plugin-serve 插件用于在本地服务器上提供静态文件,并将其作为服务提供给浏览器访问
    • rollup-plugin-livereloa 插件用于在文件发生变化时自动刷新浏览器。它会监视构建目录中的文件,当任何文件更改时,会通知浏览器自动刷新页面。
    yarn add rollup-plugin-serve rollup-plugin-livereloa -D

    rollup.config.js

    import serve from "rollup-plugin-serve";import livereload from "rollup-plugin-livereload";export default {  // ...其他配置省略  plugins: [      serve({        // 服务器启动的文件夹,访问此路径下的index.html文件        contentBase: "dist",        port: 8123      }),      // watch dist目录,当目录中的文件发生变化时,刷新页面      livereload("dist")      ]  }

    注意⚠️:它访问的是dist目录下的index.html文件,里面的内容是我们自己写的,在静态资源处理章节我们拷贝了public目录下的所有文件。

    因此,我们需要创建pubic目录并index.html,在head中使用相对路径引入打包好的umd文件,如下所示:

    <!DOCTYPE html><html lang="zh-CN"><head>  <meta charset="UTF-8">  <title>screen shot demo</title>  <script src="./screenShotPlugin.umd.js"></script>  <style>      *{          margin: 0;          padding: 0;      }      #app {          width: 100%;          height: 100%;      }  </style>  <script type="text/javascript">    const changeScreenShot = () => {      new screenShotPlugin()    }  </script></head><body><div id="app">  <div>    截图插件文字展示  </div>  <br/>  <button onclick="changeScreenShot()"> 点击截图 </button></div></body></html>

    命令行参数解析

    通常情况下我们的环境会分为开发和生产两个环境,每个环境打包出来的东西是不同的。我们在执行打包命令的时候,需要传递参数来做区分。我们想要在配置文件中读取传递过来的参数就需要用到yargs库来解析命令行参数。

    yarn add yargs -D

    rollup的配置文件如下所示,此处我需要进行解析的参数有:

    • splitCss 是否将css拆分成独立的文件,默认为内联
    • packagingFormat 打包格式,默认三种格式都打包
    • compressedState 打包后是否压缩js代码
    • showModulePKGInfo 是否启用包体积分析插件
    • useDevServer 是否启用开发服务器
    import yargs from "yargs";// 使用yargs解析命令行执行时的添加参数const commandLineParameters = yargs(process.argv.slice(1)).options({  // css文件独立状态,默认为内嵌  splitCss: { type: "string", alias: "spCss", default: "false" },  // 打包格式, 默认为 umd,esm,common 三种格式  packagingFormat: {    type: "string",    alias: "pkgFormat",    default: "umd,esm,common"  },  // 打包后的js压缩状态  compressedState: { type: "string", alias: "compState", default: "false" },  // 显示每个包的占用体积, 默认不显示  showModulePKGInfo: { type: "string", alias: "showPKGInfo", default: "false" },  // 是否开启devServer, 默认不开启  useDevServer: { type: "string", alias: "useDServer", default: "false" }}).argv;// 需要让rollup忽略的自定义参数const ignoredWarningsKey = [...Object.keys(commandLineParameters)];const splitCss = commandLineParameters.splitCss;const packagingFormat = commandLineParameters.packagingFormat.split(",");const compressedState = commandLineParameters.compressedState;const showModulePKGInfo = commandLineParameters.showModulePKGInfo;const useDevServer = commandLineParameters.useDevServer;export default {  input: "src/main.ts",  //...其他配置省略}

    我们拿到命令行的参数后,就可以在需要进行判断的地方使用了,完整配置请移步js-screen-shot/rollup.config.js

    最后,在package.json中添加构建命令传递参数即可,如下所示:

    {  "scripts": {    "build-rollup": "rollup -c --splitCss false --compState false --showPKGInfo true",    "build-rollup:dev": "rollup -wc --splitCss false --compState false --showPKGInfo true --useDServer true --pkgFormat umd",    "build-rollup:prod": "rollup -c --splitCss false --compState true"  }}

    除了使用这种方式来做区分外,你还可以通过在根目录创建多个rollup的配置文件来实现,比如要做dev模式下的打包,你可以创建rollup.dev.config.js文件。

    打包的时候,指定配置文件即可,如下所示:

    {  "scripts": {    "build-rollup:dev": "rollup -c ./rollup.dev.config.js"  }}

    配置优化

    完成文章前面的所有配置后,rollup.config.js文件中的代码总数已经200多行了,整个文件看起来乱的很,可阅读性很低。

    image-20230318170217780

    我们可以将rollup中所有需要通过参数来判断生成的配置逻辑拆分到其他文件中,在rollup.config.js文件调用拆分出来的方法,将结果渲染。

    我们在根目录创建rollup.utils.js文件,将判断逻辑封装成方法写进去,部分代码如下所示,完整代码请移步 js-screen-shot/rollup-utils.js

    import serve from "rollup-plugin-serve";import livereload from "rollup-plugin-livereload";// ...其他代码省略const enableDevServer = status => {  // 默认清空dist目录下的文件  let serverConfig = [delFile({ targets: "dist/*" })];  if (status === "true") {    // dev模式下不需要对dist目录进行清空    serverConfig = [      serve({        // 服务器启动的文件夹,访问此路径下的index.html文件        contentBase: "dist",        port: 8123      }),      // watch dist目录,当目录中的文件发生变化时,刷新页面      livereload("dist")    ];  }  return serverConfig;};export {// ...其他代码省略enableDevServer}

    在rollup.config.js引入并使用即可,部分代码如下所示,完整代码请移步 rollup.config.js

    import { enableDevServer } from "./rollup-utils";const commandLineParameters = yargs(process.argv.slice(1)).options({  //...其他配置省略})const useDevServer = commandLineParameters.useDevServer;export default {  //...其他配置省略  plugins: [    ...enableDevServer(useDevServer)  ]  }

    项目地址

    文章写到这里,我已经成功地完成了两个开源项目中的架构迁移,并且使用了文中所列出的技术。如果你想要参考我的经验,请移步我的开源项目。

    • js-screen-shot
    • screen-shot

    写在最后

    至此,文章就分享完毕了。

    我是神奇的程序员,一位前端开发工程师。

    如果你对我感兴趣,请移步我的个人网站,进一步了解。

    • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
    • 本文首发于神奇的程序员公众号,未经许可禁止转载💌


沪ICP备19023445号-2号
友情链接