前几天在调试截图插件的时候,想使用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都帮我们处理了哪些事情吧:
舍弃它,拥抱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预处理器是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
换成你所使用的预处理语言即可。
相信大家对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" } ] }) ] }
我的截图插件还有一个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
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) ] }
文章写到这里,我已经成功地完成了两个开源项目中的架构迁移,并且使用了文中所列出的技术。如果你想要参考我的经验,请移步我的开源项目。
至此,文章就分享完毕了。
我是神奇的程序员,一位前端开发工程师。
如果你对我感兴趣,请移步我的个人网站,进一步了解。