Rollup专栏是一个专门介绍Rollup打包工具的系列文章。Rollup是一个现代化的JavaScript模块打包工具,它可以将多个模块打包成一个或多个文件,以提高应用程序的性能和加载速度。
在Rollup专栏中,您将学习到如何安装和配置Rollup,以及如何使用它来打包JavaScript模块。我们将深入探讨不同类型的模块(如CommonJS、ES6等)的处理方式,以及如何处理依赖关系和循环引用。
此外,我们还将介绍一些常用的插件和配置选项,以帮助您更好地使用Rollup。您将学习到如何使用动态导入来实现按需加载,如何优化打包结果以减小文件大小,并了解与其他工具(如Babel、TypeScript等)集成使用的技巧。
通过阅读Rollup专栏,您将掌握使用这个强大工具的基本知识,并学会一些高级特性和技巧。让我们一起开始吧!
Rollup是一个JavaScript模块打包器,它可以将多个模块打包成一个单独的文件,以便在浏览器中使用。与其他打包工具相比,Rollup的主要优势在于它可以生成更小、更快的代码。在本文中,我们将深入了解Rollup的插件开发。
@rollup/pluginutils
是一个官方提供的Rollup插件开发工具库,它提供了一些实用的函数和工具,用于简化插件开发过程中的一些常见任务。
该工具库提供了以下常用的函数和工具方法:
createFilter(include?: string | RegExp | (string | RegExp)[], exclude?: string | RegExp | (string | RegExp)[]): FilterPattern
makeLegalIdentifier(name: string): string
dataToEsm(data: any, options?: DataToEsmOptions): string
attachScopes(ast: any, scope: Scope): void
这些函数和工具可以帮助开发者更方便地处理文件过滤、标识符转换、数据转换和作用域处理等常见任务,提高插件开发的效率和可靠性。
这个其实也是插件中很常用的一些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
})
],
});
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];
// 判断是否svg
const 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)专栏