前言
前几天在调试截图插件的时候,想使用yarn link
将本地打包好的文件软链接到项目里,以便于快速定位问题时,出现了错误,经过一番折腾后发现锅在Vue CLI这里。
本文就跟大家分享下我从Vue CLI迁移至Rollup的整个过程,欢迎各位感兴趣的开发者阅读本文。
为什么要更换架构
使用link
语法链接本地构建好的库,需要提供esm格式的文件,Vue CLI的打包配置选项中并没有提供这个选项。可能很多开发者跟我一样有个疑惑:既然包里没提供此格式的文件,为什么使用yarn add
一个包的时候就可以正常运行呢?我带着这个疑问查了下,发现在添加包的过程中,它会自己构建esm版本的出来。
排查到问题后,我想着可能我用的构建工具版本太老了,于是我就翻了下Vue CLI的官网,依然没找到相关的配置选项,在官网还看到了 Vue CLI is in Maintenance Mode!
的警告。
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
文件。
根据官方文档所述,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文件夹。
这就是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
文件就展示了各个模块的空间占用情况。
代码压缩
当我们要把打包后的文件部署到生产环境时,通过对打包后的文件进行压缩可以节省网络的开销,提升网站的加载速度。要实现这个功能,就需要用到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多行了,整个文件看起来乱的很,可阅读性很低。
我们可以将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)
]
}
项目地址
文章写到这里,我已经成功地完成了两个开源项目中的架构迁移,并且使用了文中所列出的技术。如果你想要参考我的经验,请移步我的开源项目。
写在最后
至此,文章就分享完毕了。
我是神奇的程序员,一位前端开发工程师。
如果你对我感兴趣,请移步我的个人网站,进一步了解。
- 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
- 本文首发于神奇的程序员公众号,未经许可禁止转载💌
评论区