推荐 最新
青椒肉丝加点糖

rollup实战(五)之用monorepo搭建组件库开发环境

前言应粉丝的建议,加更一篇使用 monorepo 搭建组件库的 playground,也就是本地的组件库测试环境之前也有一篇专门讲搭建组件库测试环境的:rollup实战(二)之用vite搭建rollup组件库的开发环境不过用 monorepo 似乎逼格更高点哈哈,所以这篇文章来分享下准备 monorepo首先准备一个空文件夹,终端执行npm init -y:是的,这是基于 npm 使用 monorepo然后创建 components 文件夹存放组件库代码进到 components 文件夹,用 npm 初始化一下npm init -y, 生成了一个 package.json:{ "name": "components", "version": "1.0.0", }我们可以把 components 当成一个变量空间,在根目录 package.json 中添加 workspace 属性:{ "name": "monorepo-demo-2", "workspaces": [ "components" ], } 没错,npm 就是通过workspace属性来管理同一文件夹下的不同项目的。每加一个项目,都需要往 workspace 中添加一个路径,表示向 npm 注册一个项目。每个项目中含有一个 package.json, npm 就是靠 json 中的 name 属性来识别不同项目上面我们完成了在一个 monorepo 中,创建并注册一个项目,主要有三步:创建文件夹 components在 components 下执行 npm 初始化在根目录中的 packagejson 注册 components这三步可以简化成一个命令npm init -w components -y。如果 components 文件夹已经存在,并且已经 npm 初始化,那么这个命令就只会执行第三步你可以将所有项目放在一个叫 packages 文件夹下面,然后在workspace 添加一个 package/*, 表示 packages 文件夹下面有多个项目,npm 靠不同项目的 package.json 中的 name 属性来识别不同项目安装依赖接下来就可以向 components 组件库安装依赖了, 在根目录执行:npm i rollup -w components这个命令的意思是向 components 项目安装一个 rollup 依赖,-w指定项目。执行完安装命令之后,就是这样:在项目根目录中生成了一个 node_modules 文件夹,里面有刚才安装的 rollup 依赖。这就是 monorepo 于普通项目的不同之处了--多个项目共享一个 node_modules,并不会在项目目录里面生成 node_modules。而且 node_modules 中还有一个 components 文件夹的软连接,也就是说 components 本身也被提升到 node_modules 中了,这是多个项目之间互相共享代码的关键。node 环境中查找依赖的顺序是,先在当前文件夹的 node_modules 查找依赖,如果没有,就往父级目录的 node_modules 中查找。逐级往上,直到查询到为止,或者查询到根目录为止然后接着安装 rollup 构建需要的其他依赖:npm i @rollup/plugin-commonjs @rollup/plugin-node-resolve -w components npm i rollup-plugin-node-external rollup-plugin-postcss -w components npm i typescript @rollup/plugin-typescript tslib -w components npm i @babel/core @babel/preset-env @babel/preset-react @rollup/plugin-babel -w components npm i react react-dom @types/react @types/react-dom -w components不得不说,rollup 组件库的依赖是真滴多编写组件库为了方便掩饰,只写一个简单的组件和构建配置文件:组件很简单,只有一个 Button,还有依赖于 Button 的 Search 组件:这是 Button 组件的内容:这是 Search 组件的内容:index.ts 作为整个组件库的入口和出口,内容是这样的:import resolve from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; import { babel } from "@rollup/plugin-babel"; import { DEFAULT_EXTENSIONS } from "@babel/core"; import typescript from "@rollup/plugin-typescript"; import nodeExternals from "rollup-plugin-node-externals"; /** @type {import('rollup').RollupOptions} */ export default { input: "./src/index.ts", output: { dir: "./dist", format: "esm", sourcemap: true, preserveModules: true, }, plugins: [ resolve(), commonjs(), typescript(), babel({ presets: [ "@babel/preset-react", [ "@babel/preset-env", { targets: ">0.2%, not dead, not op_mini all", }, ], ], exclude: /node_modules/, extensions: [...DEFAULT_EXTENSIONS, ".ts", ".tsx"], babelHelpers: "bundled", }), nodeExternals({ devDeps: true }), ], };配置项的意思就不介绍了,感兴趣可以查看我专栏的其他文章:rollup构建组件库系列试一下构建功能是否 ok。添加一个 npm script:{ "name": "components", "version": "1.0.0", "scripts": { "build": "rollup -c rollup.config.js" }, }添加了一个 build script,在 components 目录中执行npm run build后,目录中多了一个 dist 文件夹:内容没问题也可以在 monorepo 根目录中执行构建命令:npm run build -w components。这个命令的意思是执行 components 项目的 npm run build 命令如果根目录中有多个项目需要执行 npm run build 命令,可以在根目录执行npm run build -w ./表示执行当前目录下所有项目的npm run build命令准备 playground上面准备好了 rollup 组件库,还需要一个本地的开发环境 playground。为了方便,直接用 vite 脚手架搭建的 react-ts 模版。在项目根目录中执行 npm create vite playground -- --template react-ts。执行完后,就可以在可以根目录中看到生成了一个新文件夹:然后将 playground 注册到根目录的 monorepo 中,在根目录中执行npm init -y -w playground。执行完后,就可以在根目录的 package.json 中看到:基本环境准备好了,接下来就可以在 playground 中引用组件库中的组件了。不过还有件事:在 components 目录下的 packagejson 中注册组件库的入口:{ "name": "components", "version": "1.0.0", "description": "", // 注册入口 "module": "./src/index.js", }然后在 playground 中安装 components:npm i components -w playgroundplayground/package.json:{ "name": "playground", "private": true, "version": "0.0.0", "dependencies": { // 添加依赖项 "components": "^1.0.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, }上面的依赖项可以换成"components": "*"下面就可以在 playground/src/App.tsx 中引用组件库组件了:import { Button } from "components"; import "./App.css"; function App() { return ( <> <Button.Search /> <Button content='button from playground' /> </> ); } export default App;引用了 Button,以及 Search。启动项目看看效果吧。在根目录中执行npm run dev -w playground表示执行 playground 项目下面的npm run dev命令启动成功,打开网址看看:刚好是两个 button,没有问题为什么Button可以访问components呢?因为修改下代码:function App() { return ( <> <Button.Search /> <Button content='button from playground 1' /> <Button content='button from playground 2' /> </> ); }保存刷新浏览器:没有问题!playground 就搭建好了这是项目代码:zenos / monorepo-demo 刚兴趣的宝子们可以去瞅瞅总结这篇文章分享了如何使用 monorepo 来搭建组件库的 playground,本文的 monorepo 也是基于 npm 来使用的。除了 npm,也可以使用 pnpm,yarn 来搭建 monorepo,都是没有问题的。虽然本文的组件库是基于 rollup,但不限于 rollup,其他的工具也是可以的。归根结底,monorepo 就是借用了 node_modules依赖查询路径这一规律,让不同项目的代码和依赖得以共享

0
0
0
浏览量1029
青椒肉丝加点糖

rollup实战(二)之用vite搭建rollup组件库的开发环境

前言上篇文章分享了如何使用 rollup 搭建组件库,并且使用 npm link 来测试 rollup 的构建产物。最后文章遗留了一个问题,即组件库的开发环境不是很友好,测试组件还需要消费端的支持上篇文章传送:纯干货不废话,用rollup搭建组件库,再用vite搭建测试环境这篇文章来分享在组件库中,搭建一个开发环境,无需构建过程,就可以对开发的组件进行测试,并且组件的更改马上可以在浏览器中看到开发环境,我体验的最好的就是 vite 了,开箱即用,而且能实现项目的秒开,是前端项目开发的不二首选回顾回顾一下上篇文章搭建好的组件库:一共两个组件,Button 和 Input使用 rollup 构建,构建产物是 esm 格式,输出目录在 dist 中构建产物也没有打包处理,而是按照原来的文件结构输出。同时生成了对应的类型文件,放在了 types 文件夹中。这是对应代码仓库:rollup-build (github.com)搭建开发环境因为需要用到 vite,和 react,所以需要安装 vite 和 解析 react 代码的插件@vitejs/plugin-reactnpm i vite @vitejs/plugin-react然后在组件库目录中新建一个 dev 目录mkdir dev配置 vite在 dev 目录下创建一个 vite 配置文件 vite.config.jsconst { default: viteReact } = require("@vitejs/plugin-react"); const path = require("path"); /**@type {import('vite').UserConfig} */ module.exports = { Plugin: [viteReact()], server: { open: true, port: 8088, }, resolve: { alias: { "rollup-build": path.resolve(__dirname, "../src"), }, }, };配置中,使用了@vitejs/plugin-react插件来解析 react 语法。设置开发服务器端口号为 8088,并且在启动 server 之后,自动打开浏览器还设置了 alias,将rollup-build指向组件库的目录。这个很有用,为的就是在开发环境中,有真实的组件库调用体验没有 alias 之前,是这样调用的:import {Button} from '../src/Button';有了 alias 后,是这样调用的:import {Button} from 'rollup-build/Button'配置 typescript配置了 alias 后,vite 是认识了,但是 ts 并不认识,会报错:所以需要给 typescript 加上 paths 的配置,{ "compilerOptions":{ "paths": { "rollup-build/*": [ "./src/*" ] }, } }为了方便理解,paths 配置需要加在组件库中的 tsconfig.json 文件中然后再在 dev 目录下面新建一个 tsconfig.json。建好后,整个组件库就有两个 ts 配置文件了:里面一个,外面一个里面的配置文件的内容如下:{ "extends": "../tsconfig.json", "compilerOptions": { "jsx": "react-jsx" }, "include": [ "src" ] }为什么需要两个 tsconfig.json。这个说来话长了。如果只用外面的配置文件会出现什么情况?现在外面配置文件的主要内容如下:{ "compilerOptions": { "rootDir": "./src", "baseUrl": "./", "jsx": "react-jsx", "declaration": true, "emitDeclarationOnly": true, "isolatedModules": true, "outDir": "./dist/types", "lib": [ "es2020", "DOM" ], "paths": { "rollup-build/*": [ "./src/*" ] }, }, "include": [ "./src/**/*" ] }由于其中的 include 是 src 文件夹:导致 dev 中的 TS 识别出错,如果在 include 中添加 dev 文件夹的路径:tsconfig.json 配置文件又会报错主要的意思是 rootDir 应该包含所有的源文件,但是现在的rootDir为./src,与 include 范围不相同,所以报错。那好,那就修改下 rootDIr,使其范围不仅有 src 文件夹,还有 dev 文件夹:这时候 build 一下,看看会发生什么:可以看到,构建产物中的 types 文件夹下面多了一个 dev 文件夹,这是我们不想看到的。dev 只是用于开发测试,是不应该放进来的。所以只用外面的 ts 配置文件是不妥的,得两个一起用!分析内部的 ts 配置文件现在回过头来看看 ts 内部的配置文件{ "extends": "../tsconfig.json", "compilerOptions": { "jsx": "react-jsx" }, "include": [ "src" ] }首先使用 extends 继承外面的配置文件,然后include src 文件夹,表示在当前文件夹下只需要分析 src 文件中内容即可"jsx": "react-jsx"为 react 开发服务的,有了这个配置后,tsx 中不单独引入 react 就不会报错了。ts 应该给个默认值为react-jsx,不然每次都要加这个东西。看看人家 babel 都支持这功能多少年了到这里,dev 环境的配置就做好了,下面开始正式编写代码引入组件,做测试在 src 文件夹下,新建 index.tsx, App.tsx// index.tsx import * as ReactDOM from 'react-dom/client' import App from './App'; ReactDOM.createRoot(document.getElementById('root')!).render(<App />)//App.tsx import Button from "rollup-build/Button" import Input from "rollup-build/Input"; const App = () => { return <div> <Button contents='' /> <Input /> </div> } export default App;在 App.tsx 中引入了两个组件,一个是 Button,一个是 Input,路径是上面配置的 alias,指向组件库的 src 文件夹代码写好了,下面就看看是否执行 OK启动开发环境项目添加一个 npm script:{ "scripts":{ "dev": "cd ./dev && vite" } }在 dev 目录中并没有创建一个 package.json,而是在组件库的 package.json 中添加 npm script执行 npm run dev之后,就会进入 dev 文件夹之后,再去执行 vite 命令项目成功运行修改下 App.tsx 的代码:保存,浏览器自动刷新:页面上也多了 i am app热部署和自动刷新也没有问题。搞定!!这是dev目录的代码:add-dev (github.com)总结:这篇文章分享了如何为组件库项目添加开发环境。使用的 vite 工具,能够实现低配置,秒开项目的极佳体验,大赞。有了开发环境之后,就不需要等到组件库构建之后才可以测试了

0
0
0
浏览量557
青椒肉丝加点糖

rollup实战(一)之rollup搭建组件库

前言本篇文章分享如何和用 rollup 来构建一个组件库,以及使用vite搭建一个测试环境rollup 在构建组件库上,相较于 webpack 有什么优点呢rollup 基于 esm 模块作为输入模式,在构建组件库时具有更快的构建速度、更简洁的配置,更好的代码质量和更低的内存占用等优点。这些优点让 rollup 成为构建组件库的首选下面我们来看看如何使用 rollup 构建一个组件库准备两个组件首先准备两个组件,Button 和 InputButtonimport { useState } from "react" const Button = (props: { content: string }) => { const [count, setCount] = useState(0); return <div><button onClick={() => setCount(count + 1)}>count: {count}</button> <div>{props.content}</div> </div> } export default Button;Inputconst Input = () => { return <div> <span>input anything: </span> <input></input> </div> } export default Input;代码都很简单,都是非常基础的代码然后在 src 根目录下的 index 文件,将这些文件都导入并导出import Button from "./Button"; import Input from "./Input" // 导出组件文件中的非default导出 export * from './Button'; export * from './Input'; // 导出组件本身 export { Button, Input }组件准备好了,下面就开始打包了配置 rollup先在项目的根目录中创建一个 rollup 配置文件:rollup.config.js/**@type {import('rollup').RollupOptions} */ module.exports = { input: "./src/index.tsx", output: { dir: "./dist", format: "esm", sourcemap: true, }, plugins: [ //... ], external: ["react", "react-dom","react/jsx-runtime"], };小技巧:如果希望在 js 文件中,还可以获得像 ts 的编译器提示,可以使用 jsDoc,像上面的/**@type */配置中,需要指出构建的入口./src/index.tsx,以及构建产物的目录./dist,格式,是否生成 sourcemap 等信息。rollup 不仅可以打包成 esm 格式,还可以打包成 esm 格式,umd 格式,不过对于组件库而言,其应用场景是在 node 环境中的其他项目中引入,所以一般是打包成 esm。不过为了多环境的支持性,会构建 esm 和 umd 两种类型的产物plugins 中就是放插件,添加额外功能的地方了。external 表示哪些包不放进构建产物中,若它的值是["react", "react-dom"],那就表示 react 和 react-dom, 以及 react 运行时依赖react/jsx-runtime,均不打包进构建产物中添加插件rollup 作为一个构建工具,只提供一些非常基础的能力,像 commonjs 模块解析,json 解析,ts 解析,css 解析等,都不支持。好在 rollup 提供了方便简单的插件机制,并且有非常多的生命周期 hook,使得开发人员为其添加额外的功能颇为方便1先添加@rollup/plugin-commonjs, 赋予 rollup 加载第三方依赖的功能,添加@rollup/plugin-commonjs, 赋予rollup将cjc模块转成esm模块的能力npm i @rollup/plugin-commonjs @rollup/plugin-commonjs -D2添加@rollup/plugin-typescript, 赋予rolllup解析typescript的能力,还要添加相关的依赖: typescriptnpm i typescript @rollup/plugin-commonjs -D因为需要用 typescript 生成类型文件,所以还需要配置 ts 的配置文件:tsconfig.json{ "compilerOptions": { "rootDir": "./src", "baseUrl": "./", "jsx": "react-jsx", "declaration": true, "emitDeclarationOnly": true, "outDir": "./dist/types", "lib": [ "es2020" ] } }这里有几个和构建有关的,我需要重点提一下:declaration 表示生成类型文件,emitDeclarationOnly 表示只生成类型文件。因为 rollup 是借助 babel 做编译,所以只需要 ts 提供类型文件的输出即可outDir 的值是./dist/types表示 tsc 的编译产物放到当前目录下的 dist 文件夹下的 types 目录rootDIr,默认是 src,即包含所有的 ts 文件。也可以手动指定。那rootDIr是用来干什么的呢?rootDir 是和 outDir 结合起来用的。如果 rootDir 的值是./src,那么构建之后的产物就是这样的:即 src 下面的文件结构直接铺在 types 文件夹下面那如果 rootDir 的值是./,就会变成这样:即 types 文件夹下面还嵌套着一层 src。这就是 rootDir 的作用使用了@rollup/plugin-typescript,在 rollup build 的时候,就会使用 tsc 对文件进行编译,所以当我仅执行了 rollup -c 后,types 会自动产生3添加@rollup/plugin-babel, 赋予 rollup 可以解析 jsx,降级 js 语法的能力。还要添加相关的依赖:@babel/core, @babel/preset-react。因为是react 组件库,所以还需要安装 react , react-dom,@types/react, @types/react-domnpm i react react-dom @types/react @types/react-dom -D npm i @babel/core @babel/preset-react -D npm i @rollup/plugin-babel -D如果需要使用 babel 对 js 语法做降级处理,就需要安装@babel/preset-env, @babel/plugin-transform-runtime4添加rollup-plugin-postcss, postcss, 赋予 rollup 解析 css,scss,sass 的能力npm i rollup-plugin-postcss postcss -D完善 rollup 配置需要的功能都差不多了,完善下 rollup 配置文件吧const resolve = require("@rollup/plugin-node-resolve"); const postcss = require("rollup-plugin-postcss"); const typescript = require("@rollup/plugin-typescript"); const commonjs = require("@rollup/plugin-commonjs"); const { babel } = require("@rollup/plugin-babel"); /**@type {import('rollup').RollupOptions} */ module.exports = { input: "./src/index.tsx", output: { dir: "./dist", format: "esm", sourcemap: true, }, plugins: [ resolve(), commonjs(), postcss(), typescript(), babel({ presets: ["@babel/preset-react"], exclude: /nodex_module/, }), ], external: ["react", "react-dom"], };配置文件做好了,下面测试一下,看看 rollup 构建是什么样子先添加一个 npm script// package.json { "scripts": { "build": "rollup -c", } }-c的意思是,执行 rollup 时,使用 rollup 配置文件中的配置执行npm run build构建好了,花费 1.1s。 因为组件简单,就很快这是构建产物的结构:有 types 类型文件,还有 index.js 和对应的 index.js.map再看看 index.js 里面长什么样子:两个组件都被置于 index 文件中,并且格式是 esm,符合预期不 bundle 构建不过传统的 esm 都是不 bundle 的,也就是不打包成单个文件的,再改改 rollup.config.js:{ output: { dir: "./dist", format: "esm", sourcemap: true, //rollup只编译,不打包 preserveModules: true, }, }再次执行npm run build文件结构变成了这个样子:index.js 的内容:好了,构建文件做好了,该怎么测试它的可用性呢?使用 vite 搭建测试环境测试的思路是使用 vite 新建一个 react demo 项目,然后用 npm link 的方式来测试上面的构建产物mkdir use-rollup cd use-rolllup npm init -y npm i vite react react-dom @types/react @types/react-dom @vitejs/plugin-react typescript先创建一个测试项目的文件夹,然后在其中安装必要的依赖配置 vite.config.js 文件import viteReact from "@vitejs/plugin-react"; /**@type {import('vite').UserConfig} */ export default { plugins: [viteReact()], server: { port: 8080, open: "/", }, }; vite 自身支持丰富的功能,所以这里仅需要 react 插件即可。还另外配置了本地服务器配置 tsconfig.json{ "compilerOptions": { "jsx": "react-jsx", } }因为需要用到 react,所以这里配置不用引入 react 的 ts 配置,其他 ts 配置用默认的就好编写 index.tsximport ReactDOM from 'react-dom/client' import { Button } from 'rollup-build' const App = () => { return <div> App </div> } ReactDOM.createRoot(document.getElementById('root')).render(<App />)准备一个 index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=, initial-scale=1.0" /> <title>use-rollup</title> <script type="module" src="./src/index.tsx" defer></script> </head> <body> <div id="root"></div> </body> </html>将 index.html 作为 vite 启动的入口文件好了,到这里所有的东西都准备好了,这是现在的文件结构:启动项目先添加一个 npm script:"scripts": { "start": "vite" },执行npm run start:启动成功,修改几个字符看看页面也刷新了。项目没问题,下面开始测试构建产物测试构建产物首先要做到的是修改组件库项目的 package 的 main 和 types 文件指向:{ "main": "./dist/index.js", "types": "./dist/types", }mian 和 types 都指向 dist 文件夹,也就是构建产物的文件夹然后在组件库项目根目录终端执行: npm link再在 demo 目录执行npm link rollup-buildlink 成功之后,就可以在 index.tsx 中引入对应的组件保存,刷新页面:测试成功,没有问题优化测试的体验在使用 npm link 测试后,发现了个问题,Button 的 content 的属性名不对,应该是 contents,少了个 s,这时候就要修改组件库的代码了修改组件库代码之后,还需要 build,构建之后才能在 demo 项目里看见变化。那这个体验就很糟糕了,有没有什么办法可以像 vite 本地开发服务器一样,一旦文件保存,就可以在测试端看见变化呢?当然是有办法的rollup 支持 watch 模式,一旦文件变动,就会自动地单独重新构建这个文件给组件库添加一个新的 npm script:{ "build:watch": "rollup -c -w" }-w的意思就是开启 watch下面来试试:修改 Button 的属性const Button = (props: { contents: string }) => { const [count, setCount] = useState(0); return <div><button onClick={() => setCount(count + 1)}>count: {count}</button> <div>{props.contents}</div> </div> }ctrl + s 保存后,主要看看这两个文件有没有变化:这是构建之后的 Button/index.js这是 types/Button/index.d.ts均已发生变化!!很不错,看看 demo 项目中的 src/index.tsx:ts 已经提示报错了总结:这篇文章分享了如何用 rollup 来构建一个组件库。介绍了 rollup 配置的基本用法以及相关的插件。为了测试构建产物,还用 vite 来搭建一个 demo 环境,使用 npm link 来测试构建产物的可用性和正确性。最后为了测试的体验,借助了 rollup 的 watch 模式,可以在监听到文件变化后,自动重新构建。

0
0
0
浏览量572
青椒肉丝加点糖

rollup实战(三)之从开源角度去优化一个组件库

前言承接上文,我们用 rollup 搭建了一个组件库 rollup-build,但还有些需要优化的地方:构建速度不够快构建的产物只有 esm 模块,没有 umd 模块,iife 模块本篇文章内容是基于上篇文章的内容,所以了解上篇文章的内容会对阅读更有帮助传送门:👊rollup实战(二)之用vite搭建rollup组件库的开发环境构建产物准备了三种模块,每种模块都有它的作用:esm 用来支持 esmodule 环境的, 在大多数的开发环境,都是 esmodule,并且 esm 模式支持 tree shaking。所以 esm 是必要的umd 模块,支持三种模式的引入,一种是 commonjs,一种是 script,还有一种是 amd; 对于 commonjs 在一些 node 环境中是必要的。script 的场景就是在消费端将rollup-build external 的时候,就需要用 script 导入了。amd 的场景我还没遇到过对于 commonjs,我还没有遇到要用 commojs 来开发前端页面的,但是很多库都支持,咱也不能落后不是。 可能一些构建工具在引入库的时候就是 commonjs? 不过对于 script,我倒认为需求场景比较多iife 模块,iife 模块也是 script 导入使用,但相较于 umd,我更倾向于认为 iife是适配低版本浏览器,只支持 es5,甚至更低的那种。在 script 引入组件库rollup-build的依赖(ps: react, reactd-dom),然后引入组件库rollup-build,就可以直接使用。要做到这一点,就需要在 iife 的构建产物中包含所有的 es6 语法的 polyfill。下面开始准备因为需要用到三种模块的构建,所以为了项目结构清晰,需要将不同模块的构建配置文件分开。创建一个 config 文件夹,并且在里面创建三个 rollup 配置文件由于不同配置文件的插件配置是高度重合的,所以单独列出一个getPlugins.js文件处理插件,减少代码的重复性getPlugin.js// getPlugins.js import commonjs from "@rollup/plugin-commonjs"; import image from "@rollup/plugin-image"; import resolve from "@rollup/plugin-node-resolve"; import nodeExternals from "rollup-plugin-node-externals"; import postcss from "rollup-plugin-postcss"; const basePlugins = [resolve(), commonjs(), postcss({ extract: "rollup-build.css" }), image()]; 首先准备一个基础的 basePlugins,里面有各个模块都需要的基础功能,resolve() , 解析第三方依赖commonjs(), 解析 commonjs 模块postcss({ extract: "rollup-build.css" }),解析各种 css 文件格式,其中的参数表示将组件库的 css 样式都提取出来放到一个rollup-build.css文件中image(),解析各种图片格式,将图片转换为 base64 格式准备 esm 的插件// getPlugins.js import esbuild from "rollup-plugin-esbuild"; import nodeExternals from "rollup-plugin-node-externals"; export const esmPlugins = [...basePlugins, nodeExternals(), esbuild()]; 构建 esm 模块,编译插件需要处理 ts 和 react 代码,上篇文章中是用rollup-plugin-typescript处理 ts 代码,用rollup-plugin-babel处理 react 代码,输出的最后结果是 es6 代码。这份工作完全可以交给 esbuild 来做,esbuild 可以同时处理 ts 和 react 代码,而且速度还一个数量级在代码编译, esbuild 相较于 webpack,babel 会快很多nodeExternals 是用来排除所有的所有的第三方依赖的,这样组件库的构建产物体积就会足够的小准备 umd 模块// getPlugins.js export const umdPlugins = [ ...basePlugins, nodeExternals(), esbuild({ tsconfigRaw: { compilerOptions: { jsx: "react", }, }, }), ];umd 模块相比于 esm 模块,有个不同的地方--esbuild 的参数。这个参数的作用是使构建之后的 react 组件生成 reactElement 函数是 React.createElement,而不是 jsx/runtime中的函数。这是 umd 构建产物 -- Input 组件 的截图这样做的目的是用 script 引入的时候,可以少引入一个jsx/runtime的包使用了这种设置,是不是需要在组件中显示引入 React变量呢?答案是:不需要准备 iife 模块// getPlugins.js import { babel } from "@rollup/plugin-babel"; import { DEFAULT_EXTENSIONS } from "@babel/core"; export const iifePlugins = [ ...basePlugins, esbuild({ tsconfigRaw: { compilerOptions: { jsx: "react", }, }, }), // 处理低版本浏览器的polyfill babel({ presets: [ [ "@babel/preset-env", { targets: "> 0.25%, not dead, IE 10", }, ], ], exclude: /node_modules/, extensions: [...DEFAULT_EXTENSIONS, ".ts", ".tsx"], babelHelpers: "inline", //将所有的polyfill代码打包进bundle中 }), ];准备 iife 模块时,不需要将所有的第三方依赖都 external 掉,需要 external 的依赖会在 rollup.external 配置中单独列出来,这也是为了减少 script 引入产物的考量。所以这里拿掉了nodeExternals()插件因为 esbuild 并不能很好支持 es5 代码,所以,还单独加了一个 babel 插件,目的是将 es6 代码转译成 es5。其中的一些配置需要我们注意一个 targets,表示我们需要支持的浏览器数量,还有一个是 extensions,用来告诉 bable 插件,你需要它处理哪些文件。它默认是没有 ts、tsx 后缀的,所以我们额外添加。(这是坑,需要特别注意)babelHelpers 设置成 inline, 目的将 polyfill 代码都打包进构建产物中,而不是向 runtime 一样引入其中。虽然会导致构建产物增加,不过这是没有办法的事情其实要实现这个目的,babelHelpers: bundle其实是个更好的选择,但我用了这个值,构建就会报错,这是为什么:他们的区别在于 inline 会在构建产物每个文件都放一份 polyfill,而 bundle 对构建产物的所有文件都只有一个 polyfill。所以inline 模式会比 bundle 的构建产物体积更大。不过,其实对我也没有影响,因为我构建产物只有一个文件呀😄到此,getPlugins.js文件也就准备好了:// getPlugins.js import { DEFAULT_EXTENSIONS } from "@babel/core"; import { babel } from "@rollup/plugin-babel"; import commonjs from "@rollup/plugin-commonjs"; import image from "@rollup/plugin-image"; import resolve from "@rollup/plugin-node-resolve"; import esbuild from "rollup-plugin-esbuild"; import nodeExternals from "rollup-plugin-node-externals"; import postcss from "rollup-plugin-postcss"; const basePlugins = [resolve(), commonjs(), postcss({ extract: "rollup-build.css" }), image()]; export const esmPlugins = [...basePlugins, nodeExternals(), esbuild()]; export const umdPlugins = [ ...basePlugins, nodeExternals(), esbuild({ tsconfigRaw: { compilerOptions: { jsx: "react", }, }, }) ]; // script的依赖过多是不友好的 export const iifePlugins = [ ...basePlugins, esbuild({ tsconfigRaw: { compilerOptions: { jsx: "react", }, }, }), // 处理低版本浏览器的polyfill babel({ presets: [ [ "@babel/preset-env", { targets: "> 0.25%, not dead, IE 10", }, ], ], exclude: /node_modules/, extensions: [...DEFAULT_EXTENSIONS, ".ts", ".tsx"], babelHelpers: "inline", //将所有的polyfill代码打包进bundle中 }), ]; rollup.config.es.mjs这是 esm 模块的配置文件,内容很简单:// rollup.config.es.mjs import { esmPlugins } from "./getPlugins.js"; /**@type {import('rollup').RollupOptions} */ export default { input: "./src/index.tsx", output: { dir: "./dist/es", format: "esm", sourcemap: true, preserveModules: true, }, plugins: esmPlugins, }; 入口时 src 中的 index.tsx, 输出目录是dist文件夹下面的es文件夹。格式是 esm,生成 sourcemap,preserveModules: true的意思是编译不打包,构建产物仍然是按照开发的目录结构rollup.config.umd.mjs这是 umd 模块的配置文件:// rollup.config.umd.mjs import { umdPlugins } from "./getPlugins.js"; /**@type {import('rollup').RollupOptions} */ export default { input: "./src/index.tsx", output: { file: "./dist/umd/rollup-build.js", format: "umd", name: "RB", sourcemap: true, globals: { react: "React", "react-dom": "ReactDOM", }, }, plugins: umdPlugins, }; 入口和 esm 模块一样,出口是 dist 文件夹下面的 umd 文件夹。umd 还需要给出 script 场景下的全局变量名RB(取的是 rollup-build 首字母)其中 globals 的配置目的是告诉 rollup,组件库中引入的 react,react-dom,在 script 场景中,它们的全局变量名是什么。这个变量名不能是自定义的,要看具体的 react umd 格式文件中是什么那 react 举个例子:先找到 ract 中 umd 格式的文件红圈圈出来的位置,就是浏览器环境下的定义,给 global 上赋值了一个 React 属性,所以 react 库在 script 环境下的全局变量名称,就是 React红圈上面两行,分别是 commonjs 环境,和 AMD 环境,这就是为什么 umd 可以支持三种环境的原因所在了每 external 一个库,就要在 globals 中专门列出来一个,万一依赖库有十多个,那就太麻烦了。有没有一个库是专门维护这样一个映射关系的?rollup.config.iife.mjs这是 iife 模块的配置文件:// rollup.config.iife.mjs import { iifePlugins } from "./getPlugins.js"; /**@type {import('rollup').RollupOptions} */ export default { input: "./src/index.tsx", output: { file: "./dist/iife/rollup-build.js", format: "iife", name: "RB", sourcemap: true, globals: { react: "React", "react-dom": "ReactDOM", }, }, plugins: iifePlugins, external: ["react", "react-dom"], }; 相同的地方就不讲了,重点关注和其他模块配置不同的地方format 要改成 iife输出路径,是在dist/iife目录下面要额外地写出要 external 哪些库。原因之前提到过,减少 script 场景下需要额外引入的依赖,所以只将消费端大概率也会 external 的库,external 出去到此,所有模块的配置文件都准备好了,下面准备 package.jsonpackage.json{ "main": "./dist/umd/rollup-build.js", "types": "./dist/types", "module": "./dist/es/index.js", }这个配置是面向消费端的,配置了不同模块的入口。如果 commonjs 模块引入,node 就会返回 umd 下面的文件,如果是 esmodule 模块引入,node 就会返回 es 下面的文件。下面来添加构建的 npm 脚本{ "scripts": { "prebuild": "rimraf dist && tsc", "build": "npm run build:es & npm run build:umd & npm run build:iife", "build:es": "rollup -c ./config/rollup.config.es.mjs", "build:umd": "rollup -c ./config/rollup.config.umd.mjs", "build:iife": "rollup -c ./config/rollup.config.iife.mjs", }, }当执行npm run build,就会同时执行三种模块的构建,&与&&不相同,&&是串行执行,用来连接两个命令,如果前面的命令执行失败,就会阻塞后面的命令。&是并行执行,也是连接两个命令,但前后命令不会相互影响不同模块的构建并没有前后关系,所以这里用并行执行其中,还有个 prebuild 的命令,其会在npm run build之前执行。prebuild 内容中,首先是删除 dist 目录,然后执行 tsc,用来生成 ts 的类型文件,还兼有类型检查的功能。如果 ts 类型报错,就会停止构建功能。当然,报错停止构建后,类型文件也没必要输出了,可以在 tsconfig 中加上"noEmitOnError": true,就可以优化的实现这一点了。构建脚本也准备好了,下面来执行下看看吧!两个细节:可以看到 log 是交替出现了,印证了构建过程是并行执行的iife 模块构建的时间是最长的,因为用到了 babel 对 es 代码降级对比下 umd 和 iife 构建后的组件代码:可以看到 iife 中函数被转换为了 function 形式,不是箭头函数配置文件的再优化rollup 支持导出数组类型的配置文件,也就是说可以同时对不同入口,不同模块进行构建可以这么做:在 config 文件夹下面创建一个 rollup.config.js 文件://rollup.config.js import rollupConfigIife from "./rollup.config.iife.mjs"; import rollupConfigUmd from "./rollup.config.umd.mjs"; import rollupConfigEs from "./rollup.config.es.mjs"; export default [rollupConfigEs, rollupConfigUmd, rollupConfigIife];文件中将不同模块的配置文件放在一数组中重新导出package.json 中可以修改 npm run build的脚本内容:{ "scripts": { "build": "rollup -c ./config/rollup-config.js", }, }其他的构建脚本可以不删除,类似与npm run build:es,npm run build:umd,如果有特别需求,可以单独对某个模块进行构建来执行一下:可以看到, umd 构建时间显著小于 esm,也许是重用了 esm 模块的构建中间产物吧🤔总结这篇文章分享如何对 rollup 构建的组件库进行优化,优化主要两个方向,一个构建速度,构建速度用 esbuild 替代 bable;还有一个是不同模块的构建产物,构建产物分成了三种,分别是 esm, umd, iife。不同模块有其不同的特点,用来适应不同的使用场景

0
0
0
浏览量530
青椒肉丝加点糖

rollup实战(四)之测试不同模块的构建产物

前言上篇文章使用 rollup 对组件库进行构建,构建产物有三种模块,分别是 esm 模块,umd 模块,iife 模块。具体构建过程可以看这篇文章:👊rollup实战(三)之从开源角度去优化一个组件库这篇文章分享的是如何对不同模块的构建产物进行测试下面开始回顾先来回顾组件库的内容组件库中,准备了两个组件,Button,Input样式也是随便写的,长下面这样:构建产物放在了 dist 文件夹下面packagejson 中也对引入路径做了设置:{ "main": "./dist/umd/rollup-build.js", "types": "./dist/types", "module": "./dist/es/index.js", }如果调用方是在 esm 模式下引入的,就会得到 es 文件夹下面的文件;如果是在 commonjs 模式下引入的,就会得到 umd 文件夹下面的文件。iife 构建文件没有准备的入口,因为这个模块的使用场景是在浏览器中的 script,测试的时候直接 copy 这个文件到 html 文件中就可以了测试 ESM 模块现来测试 ESM 模块的构建产物在之前的文章中提到过,在另外一个项目中,通过 npm link 的方式链接到本地组件库项目,然后在代码中使用 import 语句就可以调用构建产物中的组件了因为是 import 引入的,所以会被当成是 esm 模式导入,所以测试到的文件就是 dist/es 文件夹下面的具体过程可以看这篇文章:👊rollup实战(一)之rollup搭建组件库测试 UMD 模块UMD 模块可以在 commonjs 模块中使用,也可以直接在 script 的场景直接引入。如果使用 require 的方式引入组件库组件,就会得到dist/umd下的文件,这是测试 commonjs 场景的思路。对于 script 的场景,就需要 copy 构建产物的路径到 html 文件中了。具体操作可以往下看commonjs 模块中使用在 esm 模块的测试基础上,添加一个 require 引入组件的方式就可以了。准备一个 CustomButton.jsx 文件:const { Button } = require("rollup-build"); const CustomButton = () => { return ( <div> <Button contents='custom Button' /> </div> ); }; module.exports = CustomButton;可以看到这里使用的是 commonjs 语法。然后在入口文件中引入它:// index.tsx import * as ReactDOM from 'react-dom/client' import CustomButton from './CustomButton.jsx' const App = () => { return <div> App App App <CustomButton /> </div> } ReactDOM.createRoot(document.getElementById('root')).render(<App />)保存,刷新浏览器:发现报错了,查了下,是因为 vite 并不支持 commonjs 模块的文件解析,所以需要加上一个插件://vite.config.js import commonjs from "vite-plugin-commonjs"; export default { plugins: [viteReact(), commonjs()], } 保存,再刷新浏览器:组件出现了,但是没有预期中的样式,按钮应该是蓝色,而不是原生的灰色。原来组件库在构建的时候,将 css 单独构建成一个文件了,在调用方需要单独引入。这一点和 antd 一致。//index.tsx import * as ReactDOM from 'react-dom/client' import CustomButton from './CustomButton.jsx' //引入样式文件 import "rollup-build/dist/es/rollup-build.css" const App = () => { return <div> App App App <CustomButton /> </div> } ReactDOM.createRoot(document.getElementById('root')).render(<App />) 保存,再刷新浏览器:组件正常显示,umd 模块下的 commonjs 导入没有问题搞定🤝script 场景下使用script 场景测试需要在 vite buid 之后。vite build 过程中,将 react,react-dom, 组件库 rollup-build全都 external 掉,不打包,然后在 build 之后的 index.html 中,额外将 react,react-dom,rolluo-build 都用 script 导入,最后用浏览器打开 index.html,就知道 umd 模块的构建产物在 script 场景下是否可以正常使用了组件库 package.name 是 rollup-buildexternal 需要vite-plugin-externals插件,修改下 vite 配置文件:import viteReact from "@vitejs/plugin-react"; import commonjs from "vite-plugin-commonjs"; import { viteExternalsPlugin } from "vite-plugin-externals"; /**@type {import('vite').UserConfig} */ export default { plugins: [ viteReact(), commonjs(), viteExternalsPlugin({ react: "React", "react-dom": "ReactDOM", "rollup-build": "RB", }) ], server: { port: 8080, open: "/", }, }; 配置vite-plugin-externals,将三个库都 external 出去,并且设置好每个库在全局环境下的变量名先准备一个基础 vite 项目,并且支持 react 的解析组件库在构建时,设定的全局变量名就是 RB添加一个 npm script{ "scripts": { "build": "vite build", }, }执行npm run build花费了 188ms,生成了三个文件,一个 html,一个 js,一个 css这是 html 中的内容:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>use-rollup</title> <script type="module" crossorigin src="./assets/index-8wqCi6qd.js"></script> <link rel="stylesheet" crossorigin href="./assets/index--sNeZMP7.css" /> </head> <body> <div id="root"></div> </body> </html> vite 为我们自动引入了 build 好的文件 js 和 css 文件,下面我们需要手动将 external 的库引入进 html 中。我事先将三个库的 js 文件放到了 src 同机目录下的 pro 目录中:react 和 react-dom 是从 node_modules 中拷贝来的,rollup-build 也是从 dist/umd中拷贝过来的。将这三个文件引入进 html 中:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>use-rollup</title> <script type="module" crossorigin src="./assets/index-8wqCi6qd.js"></script> <link rel="stylesheet" crossorigin href="./assets/index--sNeZMP7.css" /> </head> <body> <div id="root"></div> <!-- 添加额外的script文件 --> <script src="../prod/react.development.js"></script> <script src="../prod/react-dom.development.js"></script> <script src="../prod/rollup-build.js"></script> </body> </html> 然后启动一个本地服务器,打开这个 html 文件:显示正常,没有问题。到这里,测试 umd 模块的两个场景都已测试成功🏅iife 模块测试 iife 模块,和 umd 模块的 script 场景测试过程相同,就不赘述了测试体验优化不过,script 场景的测试有些麻烦。第一点:需要手动 copy 文件,和手动修改 index.html 文件,这不太友好。我们可以借助 vite 插件,来自动生成一个动态的 html,生成 html 可以自定义插入任何 script 脚本第二点:external 只适应于build 模式,而不能用于普通的 dev 模式,所以两者的配置文件是不相同,需要分开来针对上面两点,我们可以在项目根目录中创建一个新的配置文件 vite.config.test.js:// vite.config.test.js // 导出普通的配置文件 import viteConfig from "./vite.config.js"; import { viteExternalsPlugin } from "vite-plugin-externals"; import { createHtmlPlugin } from "vite-plugin-html"; // 生成需要插入的script脚本 const getscript = () => { const sources = ["../prod/react.development.js", "../prod/react-dom.development.js", "../prod/rollup-build.js"]; return sources .map((item) => { return `<script src='${item}'></script>`; }) .join("\n"); }; // 在原先的基础之上添加两个plugin viteConfig.plugins.push( viteExternalsPlugin({ react: "React", "react-dom": "ReactDOM", "rollup-build": "RB", }), // 基于public/index.html, 将其作为tamplate createHtmlPlugin({ minify: false, entry: "../src/index.tsx", template: "public/index.html", inject: { data: { injectScript: getscript(), }, }, }) ); export default viteConfig; 新配置文件基于原先的普通配置文件,在基础上添加了两个插件,用作测试 script 场景下的构建产物createHtmlPlugin 插件基于public/index.html,这个 html 文件需要单独准备,其中支持 ejs 语法。该插件正是基于 ejs 语法来做插入 script 脚本操作的public/index.html的内容:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>use-rollup</title> </head> <body> <div id="root"></div> <%- injectScript %> </body> </html>总结:这篇文章分享了如何测试组件库的构建产物,该测试方法不仅适用于 rollup 构建的库,还适

0
0
0
浏览量386
生成头像

qiankun 微前端 子应用不支持vite怎么办?

qiankun 微前端 子应用不支持vite怎么办?

0
1
0
浏览量147
刘一抗二二

我们应该严格遵守一个组件只有一个根元素吗?

我们应该严格遵守一个组件只有一个根元素吗? 相比于一个组件有多个根元素它有什么好的地方和不不好的地方?

0
1
0
浏览量130
我头像最美

想请问下, 若依-vue2为什么添加本地静态路由, 在菜单里不显示呢?

后端还没启动, 所以只有前端。 我把登陆校验之类的注释掉了, 直接进了若依管理系统。 可为什么我按照API的方法加路由, 菜单里不显示呢? 现在只想加点死的本地静态菜单开始写页面。 { path: '/system/test', component: Layout, hidden: false, alwaysShow: true, meta: { title: '系统管理', icon : "system" }, children: [{ path: 'index', component: (resolve) => require(['@/views/index'], resolve), name: 'Test', meta: { title: '测试管理', icon: 'user' } }] } 我未做其他任何更改。 https://wmprod.oss-cn-shanghai.aliyuncs.com/images/20241213/d193820f4b91a72f708cfeaa8a374964.png

0
1
0
浏览量19
青椒肉丝加点糖

Rollup实战:构建高效组件库与开发环境

这个专栏将带你深入学习如何使用Rollup构建高效的前端组件库,并搭建相应的开发环境。从基础搭建到优化和测试,每篇文章都涵盖了不同方面的实战内容。你将学习如何使用Rollup和Vite搭建组件库的开发环境,从开源的角度优化组件库,测试不同模块的构建产物,并了解如何使用monorepo来搭建组件库开发环境。通过这个专栏,你将掌握使用Rollup构建高效组件库所需的核心知识和实践技巧。

0
0
0
浏览量1307