✨专栏介绍
Rollup专栏是一个专门介绍Rollup打包工具的系列文章。Rollup是一个现代化的JavaScript模块打包工具,它可以将多个模块打包成一个或多个文件,以提高应用程序的性能和加载速度。
在Rollup专栏中,您将学习到如何安装和配置Rollup,以及如何使用它来打包JavaScript模块。我们将深入探讨不同类型的模块(如CommonJS、ES6等)的处理方式,以及如何处理依赖关系和循环引用。
此外,我们还将介绍一些常用的插件和配置选项,以帮助您更好地使用Rollup。您将学习到如何使用动态导入来实现按需加载,如何优化打包结果以减小文件大小,并了解与其他工具(如Babel、TypeScript等)集成使用的技巧。
通过阅读Rollup专栏,您将掌握使用这个强大工具的基本知识,并学会一些高级特性和技巧。让我们一起开始吧!
文章目录
- ✨专栏介绍
- 引言
- @rollup/pluginutils介绍
- 插件上下文
- 自定义插件
- JSON插件示例
- 图片读取
- 总结
- 😶 写在结尾
引言
Rollup是一个JavaScript模块打包器,它可以将多个模块打包成一个单独的文件,以便在浏览器中使用。与其他打包工具相比,Rollup的主要优势在于它可以生成更小、更快的代码。在本文中,我们将深入了解Rollup的插件开发。
@rollup/pluginutils介绍
@rollup/pluginutils
是一个官方提供的Rollup插件开发工具库,它提供了一些实用的函数和工具,用于简化插件开发过程中的一些常见任务。
该工具库提供了以下常用的函数和工具方法:
-
createFilter(include?: string | RegExp | (string | RegExp)[], exclude?: string | RegExp | (string | RegExp)[]): FilterPattern
- 用于创建一个过滤器,可以根据指定的包含和排除规则来过滤文件。
- 可以传入字符串、正则表达式或字符串/正则表达式数组作为参数。
- 返回一个函数,该函数接受文件路径作为参数,并返回一个布尔值,表示该文件是否应该被处理。
-
makeLegalIdentifier(name: string): string
- 用于将给定的字符串转换为合法的JavaScript标识符。
- 主要用于处理可能包含非法字符或保留字的模块名称。
-
dataToEsm(data: any, options?: DataToEsmOptions): string
- 将给定的数据转换为ES模块导出语法的字符串。
- 可以传入选项对象来自定义导出语法。
-
attachScopes(ast: any, scope: Scope): void
- 将作用域信息附加到AST(抽象语法树)节点上。
- 可以帮助插件在处理代码时正确地处理变量作用域。
这些函数和工具可以帮助开发者更方便地处理文件过滤、标识符转换、数据转换和作用域处理等常见任务,提高插件开发的效率和可靠性。
插件上下文
插件上下文
这个其实也是插件中很常用的一些api,可以通过 this
从大多数钩子中访问一些实用函数和信息位。
自定义插件
rollup-plugin-custom
import { createFilter } from '@rollup/pluginutils';
import path from "path";export default function customPlugin(options = {}) { const filter = createFilter(options.include, options.exclude);return {name: "custom-plugin",transform(code, id) { if (!filter(id)) { return null;}const parsedCode = this.parse(code);const source = `${code} \n\n ${JSON.stringify(parsedCode, null, 2)}`;const fileName = path.basename(id, path.extname(id));console.log(fileName);if (options.emitFile) { this.emitFile({type: "asset",fileName: fileName + ".txt",source})}}}}
首先,通过 createFilter
函数创建一个过滤器,用于确定哪些文件需要被处理。options.include
和 options.exclude
分别指定了需要包含和排除的文件。
然后,返回一个对象,其中包含了插件的名称和一个 transform
方法。transform
方法会在每个模块被转换时调用。 在 transform
方法中,首先使用过滤器检查当前模块是否需要处理。如果不需要处理,则返回 null
。
接下来,使用 this.parse(code)
方法解析代码,并将解析结果与原始代码拼接成一个新的字符串 source
。 然后,使用 path.basename(id, path.extname(id))
获取当前模块的文件名(不包含扩展名),并打印输出。 如果设置了 options.emitFile
为 true,则调用 this.emitFile()
方法将处理后的代码作为一个 asset 文件输出。输出的文件名为当前模块的文件名加上 .txt
扩展名。
最后,这个插件可以通过在 Rollup 配置文件中引入并添加到插件列表中来使用。
rollup.config.mjs
import { defineConfig } from "rollup";
import customPlugin from "./plugins/rollup-plugin-custom.js";export default defineConfig({input: "src/index.js",output: {dir: "dist",format: "esm",sourcemap: true,},plugins: [customPlugin({emitFile:true})],
});
JSON插件示例
rollup默认是不能直接读取json文件的内容的,我们自己写一个插件处理一下。
安装
npm install @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/pluginutils -D
rollup-plugin-json
import { createFilter,dataToEsm } from '@rollup/pluginutils';
import path from 'path';export default function myJson(options = {}) {// rollup 推荐每一个 transform 类型的插件都需要提供 include 和 exclude 选项,生成过滤规则const filter = createFilter(options.include, options.exclude);return {name: 'rollup-plugin-json',transform: {order: "pre",handler(code, id) {if (!filter(id) || path.extname(id) !== '.json') return null;try {const parse = JSON.stringify(JSON.parse(code));return {// dataToEsm 将数据转换成esm模块// 其实就是 export default "xxx"code: dataToEsm(parse), map: { mappings: '' }};} catch (err) { const message = 'Could not parse JSON file';this.error({ message, id, cause: err });return null;}}}};
}
首先,通过 createFilter
方法创建一个过滤器,用于确定哪些文件需要被处理。options.include
和 options.exclude
分别指定了需要包含和排除的文件。
然后,返回一个对象,其中包含了插件的名称和一个 transform
对象。transform
对象中有两个属性:order
和 handler
。
order: "pre"
表示这个插件在转换过程中应该在其他插件之前执行。handler(code, id)
是一个处理函数,它会在每个模块被转换时调用。
在 handler
函数中,首先使用过滤器检查当前模块是否需要处理,并且判断当前模块是否是 JSON 文件。如果不需要处理或者不是 JSON 文件,则返回 null
。
接下来,尝试将代码解析为 JSON 对象,并使用 dataToEsm(parse)
方法将解析后的对象转换为 ES 模块格式的代码。然后返回一个对象,其中包含了转换后的代码和一个空的 Source Map。
如果解析过程中出现错误,则会捕获错误并通过调用 this.error()
方法抛出错误信息,并返回 null
。
最后,这个插件可以通过在 Rollup 配置文件中引入并添加到插件列表中来使用。它会在构建过程中将 JSON 文件转换为 ES 模块格式的代码。
页面使用
import pkg from "../package.json";
console.log(pkg.name)
图片读取
mini-svg-data-uri
是一个用于将SVG图像转换为mini data URI格式的JavaScript库。它可以将SVG图像的内容转换为base64编码,并生成一个data URI,以便在HTML或CSS中直接使用。
安装mini-svg-data-uri
npm install mini-svg-data-uri -D
rollup-plugin-image
import { createFilter,dataToEsm } from "@rollup/pluginutils";
import { extname,resolve,basename,relative,normalize,sep } from "path";
import fs from "fs";
import svgToMiniDataURI from "mini-svg-data-uri";const defaults = {fileSize: 1024 * 4,target: "./dist",include: null,exclude: null,
}const mimeTypes = {".png": "image/png",".jpg": "image/jpeg",".jpeg": "image/jpeg",".gif": "image/gif",".svg": "image/svg+xml",".ico": "image/x-icon",".webp": "image/webp",".avif": "image/avif"
}const getDataUri = ({ format, isSvg, mime, source }) =>isSvg ? svgToMiniDataURI(source) : `data:${mime};${format},${source}`;const ensureDirExists = async (dirPath) => { try {await fs.promises.access(dirPath);return true;} catch (err) { // 文件夹不存在就创建文件夹try {await fs.promises.mkdir(dirPath, { recursive: true });return true;}catch (err) { console.error(err);return false;}}
}export default function myImage(opts = {}) { const options = Object.assign({}, defaults, opts);const filter = createFilter(options.include, options.exclude);return {name: "my-image",async transform(code, id) { if (!filter(id)) return null;// 获取后缀const ext = extname(id);// 判断是否是图片if(!mimeTypes.hasOwnProperty(ext)) {return null;}// 获取图片的mime类型const mime = mimeTypes[ext];// 判断是否svgconst isSvg = mime === mimeTypes[".svg"];// 图片format格式const format = isSvg ? "utf-8" : "base64";// 目标路径const assetsPath = resolve(process.cwd(), options.target);//获取文件名const fileName = basename(id);// 最终文件路径const filePath = resolve(assetsPath, fileName);let relativePath = normalize(relative(process.cwd(), filePath));relativePath = relativePath.substring(relativePath.indexOf(sep) + 1);try {// 如果图片文件过大,就应该直接拷贝文件,返回文件路径// 读取图片文件大小与设置的大小进行比较const stat = await fs.promises.stat(id);if (stat.size > options.fileSize) {// 文件的拷贝,以及对象的返回// 文件拷贝,无非就是文件源路径,目标路径// copyFile 拷贝文件地址的文件夹必须存在// 如果文件夹不存在,那么就创建文件夹const dirExists = await ensureDirExists(assetsPath);dirExists && await fs.promises.copyFile(id, filePath);return {code: dataToEsm(relativePath), //返回拷贝之后处理的路径map: { mappings: "" }}} else {// 否则转换为base64格式// 读取文件const source = await fs.promises.readFile(id, format);return {code: dataToEsm(getDataUri({ format, isSvg, mime, source })),map: { mappings: "" }}}} catch (err) { const message = "图片转换失败:" + id;this.error({ message, id, cause: err });return null;}}}
}
createFilter(include, exclude)
: 这个函数来自于@rollup/pluginutils
包,用于创建一个过滤器函数,根据给定的include和exclude规则来判断文件是否需要被处理。extname(id)
: 这个函数来自于path
模块,用于获取文件路径的扩展名。resolve(...paths)
: 这个函数来自于path
模块,用于将多个路径片段解析为绝对路径。basename(path)
: 这个函数来自于path
模块,用于获取文件路径的基本名称(不包含目录部分)。relative(from, to)
: 这个函数来自于path
模块,用于获取从一个路径到另一个路径的相对路径。normalize(path)
: 这个函数来自于path
模块,用于规范化给定的路径字符串。sep
: 这是一个常量,表示操作系统特定的路径分隔符(例如,在Windows上是反斜杠``)。fs.promises.access(path)
: 这是一个Promise-based API,用于检查指定路径是否可访问。fs.promises.mkdir(path, options)
: 这是一个Promise-based API,用于创建指定路径的目录。options参数可以包含递归选项,以便创建多级目录。fs.promises.stat(path)
: 这是一个Promise-based API,用于获取指定路径的文件信息,例如文件大小。fs.promises.copyFile(src, dest)
: 这是一个Promise-based API,用于将源文件复制到目标文件。fs.promises.readFile(path, encoding)
: 这是一个Promise-based API,用于读取指定路径的文件内容。encoding参数用于指定读取的编码格式。dataToEsm(value)
: 这个函数来自于@rollup/pluginutils
包,用于将给定的值转换为ES模块导出语法。svgToMiniDataURI(svg)
: 这个函数来自于mini-svg-data-uri
包,用于将SVG图像转换为mini data URI格式。
在插件的transform
方法中,首先使用过滤器函数判断是否需要处理当前文件。然后根据文件扩展名判断是否为图片文件,并获取对应的MIME类型。接下来根据配置的目标路径和文件名构建最终的文件路径。如果图片文件大小超过了设置的阈值,则直接拷贝该文件到目标路径,并返回拷贝后的路径。否则,将图片内容转换为base64格式,并返回对应的data URI。
rollup.config.mjs
import { defineConfig } from "rollup";
import imagePlugin from './plugins/rollup-plugin-image.js'export default defineConfig({input: "src/index.js",output: {dir: "dist",format: "esm",sourcemap: true,},plugins: [imagePlugin({fileSize: 1024 * 10,target: './dist/assets'})],
});
总结
-
Rollup插件机制允许开发者通过编写自定义插件来扩展Rollup的功能。
-
插件是由一个或多个钩子函数组成的,钩子函数定义了在打包过程中的不同阶段执行的操作。
-
常用的钩子函数有
options
、resolveId
、load
、transform
和generateBundle
,每个钩子函数都有特定的调用时机和参数。 -
插件可以通过返回一个Promise对象来处理异步操作。
-
Rollup插件可以使用第三方库来辅助开发,例如
rollup-pluginutils
用于创建过滤器。 -
开发者可以根据自己的需求编写自定义插件,并将其添加到Rollup配置中,以实现各种功能扩展,例如压缩代码、处理CSS、加载和解析JSON等。
-
插件开发需要注意性能和代码质量,避免不必要的操作和副作用。
通过使用Rollup插件机制,开发者可以灵活地定制打包过程,并根据项目需求添加各种功能扩展。这使得Rollup成为一个强大而灵活的JavaScript模块打包工具。
😶 写在结尾
前端设计模式专栏
设计模式是软件开发中不可或缺的一部分,它们帮助我们解决了许多常见问题,并提供了一种优雅而可靠的方式来构建应用程序。在本专栏中,我们介绍了所有的前端设计模式,包括观察者模式、单例模式、策略模式等等。通过学习这些设计模式,并将其应用于实际项目中,我们可以提高代码的可维护性、可扩展性和可重用性。希望这个专栏能够帮助你在前端开发中更好地应用设计模式,写出高质量的代码。点击订阅前端设计模式专栏
Vue专栏
Vue.js是一款流行的JavaScript框架,用于构建用户界面。它采用了MVVM(Model-View-ViewModel)的架构模式,通过数据驱动和组件化的方式,使开发者能够更轻松地构建交互性强、可复用的Web应用程序。在这个专栏中,我们将深入探讨Vue.js的核心概念、组件开发、状态管理、路由和性能优化等方面的知识。我们将学习如何使用Vue.js构建响应式的用户界面,并探索其强大的生态系统,如Vue Router和Vuex、Pinia。通过学习这些内容,你将能够成为一名熟练的Vue.js开发者,并能够应用这些知识来构建复杂而高效的Web应用程序。点击订阅Vue专栏
JavaScript(ES6)专栏
JavaScript是一种广泛应用于网页开发和后端开发的脚本语言。它具有动态性、灵活性和易学性的特点,是构建现代Web应用程序的重要工具之一。在这个专栏中,我们将深入探讨JavaScript语言的基本语法、DOM操作、事件处理、异步编程以及常见算法和数据结构等内容。此外,我们还将介绍ES6(ECMAScript 2015)及其后续版本中引入的新特性,如箭头函数、模块化、解构赋值等。通过学习这些内容,你将能够成为一名熟练的JavaScript开发者,并能够应用这些知识来构建出高质量和可维护的Web应用程序。点击订阅JavaScript(ES6)专栏