wbccb / Frontend-Articles

博客文章+源码调试,已经完成vue2和vue3的源码解析+webpack5整体流程源码+vite4开发环境核心流程源码+koa2源码 ===>正在研究 React18源码
MIT License
9 stars 0 forks source link

【基础】rollup使用详解 #7

Open wbccb opened 1 year ago

wbccb commented 1 year ago

本文基于rollup 3.20.2进行分析,作为后续想要使用rollup打包库(包含公共组件库)的参考资料

注:每隔一段时间会进行实时更新

Rollup是什么

Rollup 是 JavaScript 的模块打包器,它可以将小块代码编译成更大更复杂的东西,例如库或应用程序。 它使用 JavaScript ES6 修订版中包含的代码模块的新标准化格式,而不是以前的特殊解决方案,如 CommonJS 和 AMD。 ES 模块让您可以自由无缝地组合您最喜欢的库中最有用的单个函数。 这最终将在任何地方成为可能,但 Rollup 现在可以让你做到这一点。

CommonJS

Rollup 可以通过插件导入现有的 CommonJS 模块。

如果你想使用 require 和 module.exports 将配置编写为 CommonJS 模块,你应该将文件扩展名更改为 .cjs。

配置文件

在根目录使用rollup.config.js或者rollup.config.mjs进行配置

基础配置

input

入口文件地址

output

output: {
  file: './dist/my-lib.js' // 输出文件
  format: 'umd' // 输出格式: cjs, umd, iife, es6, amd
  name: 'myLib', //当format为iife和umd时必须提供,将作为全局变量挂在window(浏览器环境)下:window.myLib=...
  sourcemap: true // 生成源码映射文件
}

基础plugins

// rollup typescript配置处理
`@rollup/plugin-typescript`
/*
* 帮助寻找node_modules里的包
* rollup.js编译源码中的模块引用默认只支持ES6+的模块方式import/export。
* 然而大量的npm模块是基于CommonJS模块方式,这就导致了大量 npm 模块不能直接编译使用。
* 所以辅助rollup.js编译支持npm模块和CommonJS模块方式的插件就应运而生
*/
`@rollup/plugin-node-resolve`
// 支持import 'xx.json'文件
`@rollup/plugin-json`
// 在打包的时候把目标字符串替换å
`@rollup/plugin-replace`
// 对打包的js进行压缩
`rollup-plugin-terser`
// 删除原来的bundle
`rollup-plugin-delete`
// 显示打包后文件的大小
`rollup-plugin-filesize`
// 将CommonJs语法转成es5
`@rollup/plugin-commonjs`
// rollup 的 babel 插件,ES6转ES5
`rollup-plugin-babel`

external

告诉rollup不要把第三方库进行打包,而是作为外部依赖, 配合”peerDependencies“使用, 这样业务代码引入我们的公共库的时候避免了重复下载

// 第三方esm 语法的包
const external = [/lodash-es\/[a-z]+/, 'ts-date', 'classnames']
// 第三方cjs 语法的包
const cjsExteranl = ['classnames']

global

告诉rollup,全局变量React就是react

globals: {
    'react': 'React',
    'react-dom': 'ReactDOM'
}

其它配置

转化es6语法配置

安装插件

//rollup.dev.js
import babel from 'rollup-plugin-babel'
export default {
  input: ...,
  output: ...,
  plugins:[
    babel({
        exclude: 'node_modules/**'
    })
  ]
}

babel需要作如下配置:

比如创建.babelrc配置文件

{
"presets": [
[
"@babel/preset-env",
{
// 设置为false, 否则babel会在rollup有机会执行其操作之前导致我们的模块转化为commonjs
"modules": false,
// polyfill按需引入
"useBuiltIns": "usage",
"corejs": 3
}
],
"@babel/preset-react"
]
}

typescript配置

安装插件

import typescript from '@rollup/plugin-typescript'

const plugins = [
    typescript({
      declaration: false
    })
].filter(Boolean)

CSS配置

rollup-plugin-postcss可配置是否将css单独分离,默认没有extract,css样式生成style标签内联到head中,配置了extract,就会将css抽离成单独的文件。

rollup.config.js

import postcss from 'rollup-plugin-postcss'
import autoprefixer from 'autoprefixer'
import cssnano from 'cssnano'

export default {
  plugins:[
    postcss({
        plugins: [  
            autoprefixer(),
            cssnano 
        ]
    })
  ]
}

browserslist

使用.browserslist文件或者在package.json中配置browserslist字段

"browserslist": [
  "defaults",
  "not ie < 8",
  "last 2 versions",
  "> 1%",
  "iOS 7",
  "last 3 iOS versions"
]

CommandJS

安装插件@rollup/plugin-commonjs

import commonjs from '@rollup/plugin-commonjs'
export default {
  plugins:[
    commonjs()
  ]
}

Vue

安装插件rollup-plugin-vue@vue/compiler-sfc

rollup-plugin-vue也是默认支持scsslessstylus,可以在项目中直接使用。给.vue文件中的css自动加前缀,需要在rollup-plugin-vue中配置。

import vue from 'rollup-plugin-vue'
import autoprefixer from 'autoprefixer'
import cssnano from 'cssnano'
export default {
plugins:[
vue({
style: {
postcssPlugins: [
autoprefixer(),
cssnano()
]
}
})
]
}

代码压缩

安装插件rollup-plugin-terser

import { terser } from 'rollup-plugin-terser'
export default {
  plugins:[
    terser()
  ]
}

开发环境调试代码

安装插件rollup-plugin-serverollup-plugin-livereload

import serve from 'rollup-plugin-serve'
import livereload from 'rollup-plugin-livereload'
export default {
  plugins:[
    serve({
      contentBase: '',  //服务器启动的文件夹,默认是项目根目录,需要在该文件下创建index.html
      port: 8020   //端口号,默认10001
    }),    
    livereload('dist')   //watch dist目录,当目录中的文件发生变化时,刷新页面
  ]
}
//package.json
"scripts": {
   "dev": "rollup --watch"
},

使用Javascript API进行自定义高级打包

Rollup 提供了可在 Node.js 中使用的 JavaScript API。一般情况下不需要使用它,而应使用命令行 API,除非你要扩展 Rollup 本身或者使用它进行一些高级操作,比如通过编程生成 bundle。

rollup.rollup

rollup.rollup 函数接收输入选项对象作为参数,并返回一个 Promise,该 Promise 解析为具有各种属性和方法的 bundle 对象,如下所示。 在此步骤中,Rollup 将构建模块图并执行 tree-shaking,但不会生成任何输出。

在 bundle 对象上,您可以使用不同的输出选项对象多次调用 bundle.generate,以在内存中生成不同的 bundle。 如果直接将它们写入磁盘,请改用 bundle.write。

const rollup = require('rollup');

// 有关选项的详细信息,请参见下文
const inputOptions = {...};
const outputOptions = {...};

async function build() {
  // 创建一个 bundle
  const bundle = await rollup.rollup(inputOptions);

  console.log(bundle.watchFiles); // 该 bundle 依赖的文件名数组

  // 在内存中生成输出特定的代码
  // 您可以在同一个 bundle 对象上多次调用此函数
  const { output } = await bundle.generate(outputOptions);

  for (const chunkOrAsset of output) {
    if (chunkOrAsset.type === 'asset') {
      // 对于assets,包含
      // {
      //   fileName: string,              // asset 文件名
      //   source: string | Uint8Array    // asset 资源
      //   type: 'asset'                  // 表示这是一个 asset
      // }
      console.log('Asset', chunkOrAsset);
    } else {
      // 对于chunks, 包含
      // {
      //   code: string,                  // 生成的JS代码
      //   dynamicImports: string[],      // chunk 动态导入的外部模块
      //   exports: string[],             // 导出的变量名
      //   facadeModuleId: string | null, // 该chunk对应的模块的ID
      //   fileName: string,              // chunk的文件名
      //   imports: string[],             // chunk 静态导入的外部模块
      //   isDynamicEntry: boolean,       // 该 chunk 是否是动态入口点
      //   isEntry: boolean,              // 该 chunk 是否是静态入口点
      //   map: string | null,            // sourcemaps(如果存在)
      //   modules: {                     // 此 chunk 中模块的信息
      //     [id: string]: {
      //       renderedExports: string[]; // 导出的已包含变量名
      //       removedExports: string[];  // 导出的已删除变量名
      //       renderedLength: number;    // 模块中剩余代码的长度
      //       originalLength: number;    // 模块中代码的原始长度
      //     };
      //   },
      //   name: string                   // 命名模式中使用的 chunk 的名称
      //   type: 'chunk',                 // 表示这是一个chunk
      // }
      console.log('Chunk', chunkOrAsset.modules);
    }
  }

  // 或者将bundle写入磁盘
  await bundle.write(outputOptions);
}

build();

inputOptions

const inputOptions = {
  // 核心输入选项
  external,
  input, // 必选
  plugins,

  // 高级输入选项
  cache,
  onwarn,
  preserveEntrySignatures,
  strictDeprecations,

  // 危险选项
  acorn,
  acornInjectPlugins,
  context,
  moduleContext,
  preserveSymlinks,
  shimMissingExports,
  treeshake,

  // 实验型选项
  experimentalCacheExpiry,
  perf
};

outputOptions

const outputOptions = {
  // 核心输出选项
  dir,
  file,
  format, // 必选
  globals,
  name,
  plugins,

  // 高级输出选项
  assetFileNames,
  banner,
  chunkFileNames,
  compact,
  entryFileNames,
  extend,
  externalLiveBindings,
  footer,
  hoistTransitiveImports,
  inlineDynamicImports,
  interop,
  intro,
  manualChunks,
  minifyInternalExports,
  outro,
  paths,
  preserveModules,
  sourcemap,
  sourcemapExcludeSources,
  sourcemapFile,
  sourcemapPathTransform,

  // 危险选项
  amd,
  esModule,
  exports,
  freeze,
  indent,
  namespaceToStringTag,
  noConflict,
  preferConst,
  strict,
  systemNullSetters
};

rollup.watch

Rollup 也提供了 rollup.watch 函数,当它检测到磁盘上单个模块已经改变,它会重新构建你的 bundle。 当你通过命令行运行 Rollup,并带上 "--watch" 标记时,它将在内部使用。

const rollup = require('rollup');

const watchOptions = {...};
const watcher = rollup.watch(watchOptions);

watcher.on('event', event => {
  // event.code 会是下面其中一个:
  //   START        — 监听器正在启动(重启)
  //   BUNDLE_START — 构建单个 bundle
  //   BUNDLE_END   — 完成 bundle 构建
  //   END          — 完成所有bundle构建
  //   ERROR        — 构建时遇到错误
});

// 停止监听
watcher.close();

参考

  1. https://juejin.cn/post/6966803733871067144
  2. https://juejin.cn/post/6844904058394771470
  3. https://rollupjs.org/javascript-api/