Rollup 是一个 JavaScript 模块打包器,他可以静态分析代码中的
import
并排除任何未实际使用的代码,可以极大的缩小项目(Tree-shaking), 与 Webpack 相比,Rollup 更多的被用于类库的开发
{
// 入口 可以是一个字符串,也可以是对象
input: {
index: 'src/index.js',
},
// 出口
output: {
dir: 'es', // 可以是 dir 表示输出目录 也可以是 file 表示输出文件
format: 'es', // 输出的格式 可以是 cjs commonJs 规范 | es es Module 规范 | iife 浏览器可引入的规范
sourceMap: true,
entryFileNames: '[name]/index.js',
exports: 'named'
},
// 是否开启代码分割
experimentalCodeSplitting: true,
// 需要引入的插件
plugins: [
clear({
targets: ['es']
})
],
// 将模块视为外部模块,不会打包在库中
external: id => external.some(e => id.indexOf(e) === 0),
// 文件监听
watch: {
include: 'src/**',
clearScreen: true
}
};
使用到的插件
rollup-plugin-clear
做打包前的文件夹清理rollup-plugin-postcss
样式文件编译处理rollup-plugin-eslint
代码规范检查rollup-plugin-flow
flow 相关类型检查代码清理rollup-plugin-babel
编译rollup-plugin-commonjs | rollup-plugin-node-resolve
帮助查找以及转化外部模块主要通过
rollupConfig.external
配置项实现,但是需要 babel 做一些辅助处理
/*
external 是一个从外部传入的数组表示不需要被打包的包
通过判断当前处理的包的名称是否已数组中的包名开头,来将其排除
*/
rollupConfig.external = id => external.some(e => id.indexOf(e) === 0);
plugin: [
babel({
exclude: 'node_modules/**', // 只编译源代码
runtimeHelpers: true
})
]
{
"presets": [
["env", {
"modules": false,
"targets": {
"node": "current",
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"flow",
"react",
"stage-2"
],
"plugins": [
"external-helpers", // 这里
["import", { "libraryName": "antd", "libraryDirectory": "es", "style": true }]
]
};
rollup 提供代码分割功能,主要原理是配置多入口文件,这样每个入口文件都会对应一个分割包,各个分割包的共有部分会被提取为 chunk
// 通过 mode 接口拿到 src/components 下的所有文件夹名作为打包后的模块
const fs = require('fs');
const path = require('path');
const componentDir = 'src/components';
const cModuleNames = fs.readdirSync(path.resolve(componentDir));
const cModuleMap = cModuleNames.reduce((prev, name) => {
prev[name] = `${componentDir}/${name}/index.js`;
return prev;
}, {});
export default cModuleMap;
{
input: {
index: 'src/index.js',
... cModuleMap
}
}
{
output: {
dir: 'es',
format: 'es',
entryFileNames: '[name]/index.js', // 输出文件名
exports: 'named'
}
}
{
experimentalCodeSplitting: true
}
通过 rollup-plugin-postcss 可以实现 样式文件的抽离,但是只能抽离为一个包,这对组件库组件样式的按需引入是不利的
所以使用了一个折中的方式:鉴于 rollup 的配置文件既可以是一个对象也可以是一个数组,使用数组时可以依次执行其中的每一项配置,由此,我们样式抽离与js 的 分模块打包分开处理,其中一个配置负责 js 的打包,其余配置负责样式的抽离,如下:
/*
rollup 配置文件
*/
import pkg from './package.json';
import createModuleConfig from './config/rollupRenderModuleConfig';
import createStyleConfig from './config/rollupRenderStyleConfig';
import cModuleMap from './config/obtainComponentsName';
const external = Object.keys(pkg.dependencies);
const isDev = process.env.NODE_ENV === 'development';
/*
dev 情况下不做样式抽离
其他环境下,除了基本的 js 打包外,遍历要拆分的模块,分别生成一个配置项,在这个配置项中处理各自的样式分离
*/
const rollupConfig = isDev
? createModuleConfig(cModuleMap, external, isDev)
: [
createModuleConfig(cModuleMap, external, isDev)
].concat(
Object.keys(cModuleMap).map(moduleName => createStyleConfig(moduleName, external))
);
export default rollupConfig;
/*
rollup 配置文件
*/
import postcss from 'rollup-plugin-postcss';
import eslint from 'rollup-plugin-eslint';
import clear from 'rollup-plugin-clear';
import basePlugin from './rollupBasePluginConfig';
const createModuleConfig = (cModuleMap, external, isDev) => ({
input: {
index: 'src/index.js',
...cModuleMap
},
output: {
dir: 'es',
format: 'es',
sourceMap: true,
entryFileNames: '[name]/index.js',
exports: 'named'
},
experimentalCodeSplitting: true,
plugins: [
clear({
targets: ['es']
}),
postcss({
// modules: true, // 增加 css-module 功能
extensions: ['.less', '.css'],
use: [
['less', {
javascriptEnabled: true
}]
],
inject: isDev, // dev 环境下的 样式是入住到 js 中的,其他环境不会注入
extract: false // 无论是 dev 还是其他环境这个配置项都不做 样式的抽离
}),
eslint({
include: ['src/**/*.js']
}),
...basePlugin
],
// 将模块视为外部模块,不会打包在库中
external: id => external.some(e => id.indexOf(e) === 0),
...(isDev ? {watch: {
include: 'src/**',
clearScreen: true
}} : {})
});
export default createModuleConfig;
import postcss from 'rollup-plugin-postcss';
import clear from 'rollup-plugin-clear';
import basePlugin from './rollupBasePluginConfig';
const createStyleConfig = (moduleName, external) => ({
input: `src/components/${moduleName}/index.js`,
output: {
file: `garbage/${moduleName}.js`,
format: 'es',
},
plugins: [
clear({
targets: ['garbage']
}),
// css 处理,暂时没有加插件
postcss({
// modules: true, // 增加 css-module 功能
extensions: ['.less', '.css'],
use: [
['less', {
javascriptEnabled: true
}]
],
// 样式输出到 createModuleConfig 创建的模块文件夹下
extract: `es/${moduleName}/style/index.css`
}),
...basePlugin
],
external: id => external.some(e => id.indexOf(e) === 0),
});
export default createStyleConfig;
参考了 antd 的按需引入方式:通过 babel-plugin-import 插件,在 babel 运行时,将类似
import { ModuleName } from 'libiaryName';
的代码转化为组件所在的路径,这样实际引用的就是这个组件的模块而不是整个 Library
// babel 前
import { ModuleName } from 'libiaryName';
// babel 后
import ModuleName from 'libiaryName/ModuleName/index.js';
import 'libiaryName/ModuleName/style/index.css';
查看 babel-plugin-import 的文档,要实现同样的功能需要对打包后的文件夹做些规范,如下,具体代码查找上边 postcss 配置
// es/ 下
├── LoadingButton
│ ├── index.js
│ └── style
│ └── index.css
├── TestButton
│ ├── index.js
│ └── style
│ └── index.css
├── TestLodash
│ └── index.js
├── TestWeb
│ ├── index.js
│ └── style
│ └── index.css
├── chunk-3a85a70c.js
├── chunk-c05d6a54.js
└── index
└── index.js
项目中引用 配置 babel
"plugins": [
// babel 7 以下如下配置 第二个参数用数组
["import", [
{ "libraryName": "antd", "libraryDirectory": "es", "style": true },
{
"libraryName": "xxx-ui",
"libraryDirectory": "es",
"camel2DashComponentName": false,
"style": true
}
]]
// babel 7 以上如下配置 可以写多个 import 配置
["import", {
"libraryName": "antd", "libraryDirectory": "es", "style": true
}],
["import", {
"libraryName": "xxx-ui",
"libraryDirectory": "es",
"camel2DashComponentName": false,
"style": true
}]
]
注意 camel2DashComponentName 设置转换的文件夹格式 antd 默认是小写的带连字符的,我们这里用的是大写的驼峰命名
创建地址
npm login # 输入用户名 密码
npm publish # 需要确保 package.json version 与上一个版本不一样
有一些 字段需要注意
{
"name": "xxx-ui", // 包名
"version": "0.1.4", // 版本,每次都要不一样
"main": "es/index/index.js", // 规范的,定义程序入口文件
"module": "es/index/index.js", // 新的,定义使用 es 模块的入口文件
"files": [ // npm publish 中约定可上传的文件夹
"dist",
"src",
"es"
],
"directories": {
"es": "es"
},
"publishConfig": { // 设置模块发布时发布到的镜像仓库地址,默认是你的 npm registry 指向的地址
"registry": "http://localhost:8081/repository/xxx-ui/"
}
}