背景. 为什么会出现rollup?
为什么会出现rollup? webpack中的import本质上并不是es6标准的import,而是webpack自己定义了一个import,因此在打包文件的时候会出现大量的webpack_import函数体,这无疑增加了打包文件的体积。而rollup是面向ES6规范的,因此rollop中的import就是ES6中的import,所以无需在“二次处理”import,直接就可以获取模块中的导出。 例如:以下为ES6代码,使用了import关键字。
// 入口main。js
import { b } from './test/a'
console.log(b + 1)
console.log(1111)
// './test/a'
export const b = 'xx'
export const bbbbbbb = 'xx'
问题:那么如果另一个模块叶定义了const b,那么最后导入main.js文件中不是有两个b变量,造成了变量污染?
然后我们看下rollup的打包文件,很自然就拿到了导出值 b = 'xx',说明rollup中的import是交给ES6底层去处理的,不是自己处理。这样打包出的文件可以利用这一点,将众多的模块“很干净”的合并到一个文件中。
const b = 'xx';
console.log(b + 1);
console.log(1111);
但是反观“杜兰特”:webpack生成的打包文件就很大了:
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "./";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);
// CONCATENATED MODULE: ./src/test/a.js
const b = 'xx';
const bbbbbbb = 'xx';
// CONCATENATED MODULE: ./src/main.js
console.log(b + 1);
console.log(1111);
/***/ })
/******/ ]);
总结: 由此可以看出,rollup最大的特色就是使用了ES6规范中import来导入模块内容,这样极大的节省了打包后的体积(免去了模块化处理代码),此外还基于ES6实现了代码的静态分析tree-shaking。可以说是彻底拥抱了ES6+!
webpack解惑: 问题1:为什么webpack打包体积比rollup大? 答:因为 webpack 本身维护了一套模块系统,将所有的模块都被封装成function (module, webpack_exports, webpack_require){}的形式,然后每个模块都统一存储在对象中,且对应自己的模块ID,这样不同模块就能被区分。每个模块的导出值记录在module.exports中。这套模块系统兼容了所有前端历史进程下的模块规范,包括amd、commonjs、es6等
问题2:为什么webpack支持import和require两种引入模块的方式
mport的本质其实就是require,webpack在进行静态分析时会将其转为require。因此webpack中的import和require本质上都是require。(这也侧面印证了webpack是基于node的,node的模块规范是commonjs,该规范就是使用require和module.exports来引入和导出模块的,虽然es6 module是静态的依赖,但是webpack并没有使用静态导入,而是在自己静态分析阶段解析出“import”为“require”,以达到欺骗效果。)
参考:www.jianshu.com/p/812347b4a…
一、rollup的配置
1.1 rollup.config.js文件的基本配置
需要在package.json中的 script字段 进行配置:
"scripts": {
"build": "node_modules/.bin/rollup -c ./build/rollup.config.js",
"dev": "node_modules/.bin/rollup -w -c ./build/rollup.config.js"
},
// 相应命令:
1) -c命令为`--config`的缩写,表示设置`rollup`打包的配置。
2) -w命令为`--watch`的缩写,在本地开发环境添加`-w`参数可以监控源文件的变化,自动重新打包。
为什么rollup.config.js文件可以执行相应的配置,是rollup和webpack一样自动去寻找config文件还是w我们认为在命令行中设置的呢? 可以参考以下文章 blog.cjw.design/blog/old/ro…
(1) 配置plugin插件 和 presets预设
-
presets plugins插件的集合,如['es2015’] 数组,表示插件集合
-
plugins 按需引进,拆成细小粒度的插件,如['transform-es2015-arrow-functions'、'transform-es2015-template-literals'] 数组,表示插件。
1.babel插件
babel是保证es6代码可以在不同版本的浏览器进行兼容运行的重要工具。而babel配置和核心就在于「preset」和「plugins」,这两项的配置决定了可以使用哪些es的语法特性。
plugins和preset的区别
babel插件为了尽可能的实现按需引入,因此拆分了很多plugin插件,但是当我们需要实现“ES6到ES5的转化”功能的时候,涉及到很多拆分的小的plugins插件,此时我们如果依然一个个导入配置插件 - plugins的话,就会造成一些繁琐。因此如果当前项目需要用到 ES6 转 ES5 的大部分功能,那么更适合直接引用插件的集合 - presets 。plugin和presets的执行顺序:
module.exports = { presets: [E,D], plugins: [A,B,C] }总结:presets是多个plugin功能组合后的包
目前babel维护着四个presets集合包,分别是:preset-env、preset-flow、preset-react、preset-typescript。而在rollup中我们主要需要使用到的是babel的preset-env这个集合plugins。可以从env看出,这个是babel为了解决市面上不同版本的浏览器兼容问题,专门配置的一个浏览器版本列表,然后会根据这个版本列表自动抓华为当前版本浏览器可以运行的js。
(2) 配置本地http服务
和webpack一样,rollup如果需要本地服务,那么需要rollup-plugin-serve插件实现。
1.package.json文件进行开启http服务命令配置
"scripts": {
"dev": "node_modules/.bin/rollup -w -c ./build/rollup.config.js"
},
// 相应命令:
1) -c命令为`--config`的缩写,表示设置`rollup`打包的配置。
2) -w命令为`--watch`的缩写,在本地开发环境添加`-w`参数可以监控源文件的变化,自动重新打包。
2.rollup.config.js文件中配置http服务器端口
const path = require('path');
const { babel } = require('@rollup/plugin-babel');
const serve = require('rollup-plugin-serve'); // 1.引入serve插件
const resolveFile = function(filePath) {
return path.join(__dirname, '..', filePath)
}
module.exports = {
input: resolveFile('src/index.js'),
output: {
file: resolveFile('dist/index.js'),
format: 'umd',
sourcemap: true,
},
plugins: [
babel({
presets: ['@babel/preset-env']
}),
serve({ // 2.在plugin中配置serve服务器的端口和文件
open: true, // 运行时自动打开浏览器
headers: { "Access-Control-Allow-Origin": "*" }, // 本地服务允许跨域
port: 3001, // 设置网络服务监听端口
contentBase: [resolveFile('example'), resolveFile('dist')] // 本地服务的运行文件根目录
})
],
}
执行npm run dev之后的控制台显示如下:访问localhost:3001相当于访问到路径/Users/luweidong/Desktop/RollupProjects/example和/dist/index.js
1.2 两种打包方式
(1) bin命令打包
我们在执行rollup打包的时候,其实是在执行json文件中的script命令,而这个命令实际上是在执行node_modules下的bin文件夹下面的rollup文件。
1.package.json中的「bin」字段
一般来说,我们的常规开发项目不会需要bin字段,但是如果是npm包的话,那么其对应的json文件就需要bin字段。 当我们使用npm install安装npm包时候,如果这个包json文件存在bin字段,那么npm就会通过这个npm包中bin字段对应可执行文件路径,找到该可执行文件,然后复制到项目的node_modules下的bin目录下。
我们在运行npm run start的时候,实际上是在执行json文件中的script字段中的“dstart”配置,"scripts": {"start": "webpack"},而实际上则是"scripts": {"start": "node_modules/.bin webpack"}, 因为npm已经默认给script字段加上了node_moudles/.bin前缀
如下所示,rollup的package.json文件的bin字段值:
然后npm会通过路径dist/bin/rollup找到对应可执行文件,复制到项目的node_module目录下:
以此看来,我们执行npm run build的时候,最终就是执行node_modules/.bin/rollup这个可执行文件,然后按照这个可执行文件的代码去自动操作打包。
"scripts": { "dev": "node_modules/.bin/rollup -w -c ./build/rollup.config.dev.js", "build": "node_modules/.bin/rollup -c ./build/rollup.config.prod.js" },此外,对应vue的命令也是通过bin字段去配置的,如果执行vue create命令去创建项目时候,本质上也是执行对应的bin文件,然后创建项目的。
总结:bin字段值是npm包可执行文件路径,npm install之后会将可执行文件放置在node_module中,然后在script中的字段就是执行bin可执行文件。 www.jianshu.com/p/53feedd72…
(2) rollup-API打包
rollup-API打包,即为rollup.js的API在Node.js代码中执行编译代码。如下所示为
// package.json文件
"scripts": {
"build": "node ./build/build.js"
},
// build/build.js文件
const rollup = require('rollup');
const config = require('./rollup.config'); // 引入config.js相关配置
const inputOptions = config;
const outputOptions = config.output;
async function build() {
// create a bundle
const bundle = await rollup.rollup(inputOptions);
console.log(`[INFO] 开始编译 ${inputOptions.input}`);
// generate code and a sourcemap
const { code, map } = await bundle.generate(outputOptions);
console.log(`[SUCCESS] 编译结束 ${outputOptions.file}`);
// or write the bundle to disk
await bundle.write(outputOptions);
}
build();
二、js模块化编译
Rollup支持将ES6代码编译成支持的模块化形式,具体包括:AMD、commonjs、UMD、IIFE四种模块化表现形式
2.1 amd 编译
2.2 cjs 编译
2.3 umd 编译
三、CSS编辑
四、框架编译(Rollup打包vue)
step1: 安装rollup和vue3
在根目录下安装rollup和vue3
npm install rollup
npm install --save vue@next
step2: 构建必要插件
主要编译解析插件
1.处理vue文件
npm i --save-dev rollup-plugin-vue @vue/compiler-sfc
通过这两个插件来联合解析处理vue文件(相当于webpack中的vue-loader)。因为rollup只能解析js文件,因此对于框架文件的解析需要对应的编译插件。vue组件中包含三块,template、script、style等三块代码,分别表示模版、脚本、样式三块。安装好这两个插件后,会把单文件组件编译为标准的 JavaScript(js文件中含有CSS-style)。
如下所示,是一个vue文件通过SFC解析后得到的js文件,文件中存一个js对象。其中vue的style 部分则是被 parse 成一个数组styles,它的类型是SFCStyleBlock[]。为什么 style 的 parse 结果会是一个数组呢?这是因为我们可以在 .vue 文件中写多个 style 块。(然后在会将这个AST合并为render)。
因此,如果没有单独配置的话,vue组件内的样式还是会跟script生成的js文件打包在一起。我们在webpack中使用vue-loader来解析.vue文件,其底层也是调用了@vue/compiler-sfc 插件使用:我们在rollup.config.js配置文件中,引入这个rollup-plugin-vue插件,并在plugins中进行注册,就可以正常编译、打包.vue文件了
// rollup.config.js配置文件 import vue from 'rollup-plugin-vue' // 引入可以解析vue的插件 export default { ... plugins:[ vue() ] }
2.处理样式
npm i --save-dev rollup-plugin-css-only
插件
rollup-plugin-css-only是一个用于提取并输出 CSS 样式文件的插件。当使用 Rollup 构建项目时,通常将 CSS 代码打包到 JavaScript 文件中。但在某些情况下,例如在使用服务端渲染时,您可能需要将 CSS 样式文件分离出来。上面的vue文件拆分出来了js和css代码,那么我们就可以使用这个插件,将css单独打包出来。但是这里需要注意的是,虽然可以抽离出vue的style样式到单独的文件中,但是是将所有的vue-style合并到一个css文件的。每个样式中[data-v-hash值]来实现vue-scoped。 使用rollup-plugin-css-only插件了。至此插件配置为// rollup.config.js配置文件 import vue from 'rollup-plugin-vue' // 引入可以解析vue的插件 import css from 'rollup-plugin-css-only' // 抽离css为单独文件 export default { ... plugins:[ vue({css: false}), css() ] }
3.编译npm模块
npm i --save-dev @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-replace
@rollup/plugin-commonjs 插件
作用:支持对commonjs模块的打包。由于rollup本身只支持ES模块进行打包,但是如果我们在开发中引用的依赖包是commonjs模块的npm包,那么rollup在解析引入的这个npm包是无法成功的,因此需要这个插件将这个CommonJS 模块转换为 ES 模块,然后再一起打包。这个插件还支持一些高级功能,比如解析动态导入语法和处理循环依赖。通过在 Rollup 配置文件中添加 @rollup/plugin-commonjs,你可以更方便地使用 Rollup 打包项目中的 CommonJS 模块。@rollup/plugin-node-resolve 插件
主要解决了以下两个问题:
问题1.Node.js 模块识别 - 如 Rollup 打包项目时,可能会遇到一些需要从node_modules 目录导入的模块,而这些模块并不是采用 ES module 规范,或者它们的入口文件并未指定为 ES module。在这种情况下,为了确保 Rollup 能够正确找到并导入这些模块,您可以通过配置 rollup-plugin-node-resolve 插件来将其转换为适用于 Rollup 的 ES module 格式。但是,如果遇到commonjs模块的话,该插件并不能将其转为es-module,还是需要借助@rollup/plugin-commonjs来将commonjs转为ES-Module。此外,插件会解析这些导入对应的文件路径。它支持查找 .js, .json, .node 文件,以及其他可配置的扩展名。
问题2别名(aliases)和自定义模块解析逻辑 - 如果你的项目使用了模块别名或者有特定的模块解析规则,你可以通过配置此插件来实现。这样可以使 Rollup 更好地适应各种开发环境和团队规范。@rollup/plugin-replace 插件
作用:在打包过程中替换代码中的特定字符串,我们常用的环境变量替换就是使用该插件。如下图为替换环境变量:将替换所有包含在构建中的文件中的每个实例中的字符串‘process.env.NODE_ENV’ 替换为:‘production’。对于复杂的值,请使用 JSON.stringify。import replace from '@rollup/plugin-replace'; export default { input: 'src/index.js', output: { dir: 'output', format: 'cjs' }, plugins: [ nodeResolve(), commonjs(), replace({ 'process.env.NODE_ENV': JSON.stringify('production'), // 上面的配置将替换所有包含在构建中的文件中的每个实例 `process.env.NODE_ENV` 为 `production` }) ] };
4.编译ES6+模块
npm i --save-dev @rollup/plugin-buble @rollup/plugin-babel @babel/core @babel/preset-env
- @rollup/plugin-buble插件: 编译简单的ES6代码
- @rollup/plugin-buble插件:rollup的ES6编译插件
- @babel/core 插件:是官方 babel 编译核心模块
- @babel/preset-env 插件:是官方 babel 编译解析ES6+ 语言的扩展模块
babel插件为了尽可能的实现按需引入,因此拆分了很多babel-plugin插件。但是当我们需要实现“ES6到ES5的转化”功能的时候,涉及到很多拆分的小的plugins插件,此时我们如果依然一个个导入配置插件 - plugins的话,就会造成一些繁琐。因此babel推出了plugins的功能集合presets,而@babel/preset-env就是一个常用的presets。(依赖于plugin-buble && core插件),因此我们不用在plugins中一次调用plugin-buble插件和core插件,而是直接配置preset-env插件即可。
plugins: [ babel({ presets: ['@babel/preset-env'] // 配置presets相当于连续调用plugins(). }), ],
5.其他安装
1)编译本地开发服务插件:
npm i --save-dev rollup-plugin-serve
2)编译代码混淆插件:npm i --save-dev rollup-plugin-uglify
step3: package.json文件配置
项目下的package.json文件
{
"name": "rollup-vue",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "node_modules/.bin/rollup -w -c ./build/rollup.config.dev.js",
// 执行命令指向build中的配置文件
"build": "node_modules/.bin/rollup -c ./build/rollup.config.prod.js"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.9.6",
"@babel/preset-env": "^7.9.6",
"@rollup/plugin-babel": "^5.0.0",
"@rollup/plugin-buble": "^0.21.3",
"@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-node-resolve": "^7.1.3",
"@rollup/plugin-replace": "^2.3.2",
"@vue/compiler-sfc": "^3.0.0-beta.6",
"rollup": "^2.7.6",
"rollup-plugin-css-only": "^2.0.0",
"rollup-plugin-serve": "^1.0.1",
"rollup-plugin-uglify": "^6.0.4",
"rollup-plugin-vue": "^6.0.0-alpha.7"
},
"dependencies": {
"vue": "^3.0.0-beta.6"
}
}
step4: rollup.config.js 配置 && 环境配置
配置文件build目录:包含统一的配置文件,以及区分dev和prod环境的配置。
1. rollup.config.js
// rollup.config.js
const path = require('path');
const buble = require('@rollup/plugin-buble');
const { babel } = require('@rollup/plugin-babel');
const nodeResolve = require('@rollup/plugin-node-resolve');
const commonjs = require('@rollup/plugin-commonjs');
const vue = require('rollup-plugin-vue');
const css = require('rollup-plugin-css-only');
const replace = require('@rollup/plugin-replace')
const resolveFile = function(filePath) {
return path.join(__dirname, '..', filePath)
}
// const isProductionEnv = process.env.NODE_ENV === 'production';
const babelOptions = {
"presets": ['@babel/preset-env'],
}
module.exports = [
{
input: resolveFile('src/index.js'),
// input: resolveFile('src/App.vue'),
output: {
file: resolveFile('dist/index.js'),
format: 'iife',
name: 'App'
},
// external: ['vue'],
plugins: [
vue({css: false}),
css(),
nodeResolve(),
commonjs(),
replace({
'process.env.NODE_ENV': JSON.stringify( 'production' )
}),
babel(babelOptions),
buble(),
],
},
]
2. rollup.config.dev.js -开发环境配置
process.env.NODE_ENV = 'development';
const path = require('path');
const serve = require('rollup-plugin-serve');
const configList = require('./rollup.config');
const resolveFile = function(filePath) {
return path.join(__dirname, '..', filePath)
}
const PORT = 3001;
const devSite = `http://127.0.0.1:${PORT}`;
const devPath = path.join('example', 'index.html');
const devUrl = `${devSite}/${devPath}`;
setTimeout(()=>{
console.log(`[dev]: ${devUrl}`)
}, 1000);
configList.map((config, index) => {
config.output.sourcemap = true;
if( index === 0 ) {
config.plugins = [
...config.plugins,
...[
serve({
port: PORT,
contentBase: [resolveFile('')]
})
]
]
}
return config;
})
module.exports = configList;
3. rollup.config.prod.js 线上配置
process.env.NODE_ENV = 'production';
const { uglify } = require('rollup-plugin-uglify');
const configList = require('./rollup.config');
const resolveFile = function(filePath) {
return path.join(__dirname, '..', filePath)
}
configList.map((config, index) => {
config.output.sourcemap = false;
config.plugins = [
...config.plugins,
...[
uglify()
]
]
return config;
})
module.exports = configList;
step5. 开发文件
1. index.js文件
import Vue from 'vue/index.js';
import App from './App.vue';
Vue.createApp(App).mount('#App');
2. APP.vue文件
<template>
<div>
<h1>Hello {{ name }}</h1>
12312312
<!-- <Text></Text> -->
</div>
</template>
<script>
// import Text from './text.vue'
export default {
components: {
Text
},
data() {
return { name: 'World!' }
}
}
</script>
<style scoped>
h1 {
color: red;
}
</style>
step6. example测试
1. index.html文件
<html>
<head>
<script src="https://cdn.bootcss.com/babel-polyfill/6.26.0/polyfill.js"></script>
<link rel="stylesheet" href="./../dist/index.css" />
</head>
<body>
<p>hello rollup + vuejs</p>
<div id="App"></div>
<script src="./../dist/index.js"></script>
</body>
</html>
五、多文件输入输出编译(一套代码多种编译)
单入口单出口、多入口/多出口blog.csdn.net/iamyang0511…
export default [
{
input: 'main-a.js',
output: {
file: 'dist/bundle-a.js',
format: 'cjs'
}
},
{
input: 'main-b.js',
output: [
{
file: 'dist/bundle-b1.js',
format: 'cjs'
},
{
file: 'dist/bundle-b2.js',
format: 'es'
}
]
}
];
单入口/多出口 www.51cto.com/article/781…
六、按需加载实现
问题四: rollup 的 plugin 机制是怎么样的 ?如何实现一个自定义 plugin ?
问题五: rollup 的整个工作过程是怎么样的 ?
问题6: 如何使用babel
babel中的plugin和presets是
问题七: rollup 的 treeshaking 原理是什么 ?
rollup使用场景
如果你的项目是以插件或库给用户引入使用,Rollup 是最佳选择,因为它可以将代码转成不同的模块,然后用户根据自身项目模块语法来引入你的代码。
未完待续