rollup的介绍
什么是rollup
Rollup 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码
为什么用rollup
-
rollup
打包出来的体积都比webpack
略小一些,通过查看打包出来的代码,webpack打包出来的文件里面有很多__webpack_require__
工具函数的定义,可读性也很差,而rollup打包出来的js会简单一点。 -
如果你将项目拆分成小的单独文件中,这样开发软件通常会很简单,因为这通常会消除无法预知的相互影响(remove unexpected interaction),以及显著降低了所要解决的问题的复杂度(complexity of the problem),并且可以在项目最初时,就简洁地编写小的项目(不一定是标准答案)。不幸的是,JavaScript 以往并没有将此功能作为语言的核心功能。
-
可以使用
rollup
插件解析.vue
文件。
Tree-shaking#
除了使用 ES6 模块之外,Rollup 还静态分析代码中的 import,并将排除任何未实际使用的代码。这允许您架构于现有工具和模块之上,而不会增加额外的依赖或使项目的大小膨胀。
例如,在使用 CommonJS 时,必须导入(import)完整的工具(tool)或库(library)对象。
// 使用 CommonJS 导入(import)完整的 utils 对象
var utils = require( 'utils' );
var query = 'Rollup';
// 使用 utils 对象的 ajax 方法
utils.ajax( 'https://api.example.com?search=' + query ).then( handleResponse );
但是在使用 ES6 模块时,无需导入整个 utils
对象,我们可以只导入(import)我们所需的 ajax
函数:
// 使用 ES6 import 语句导入(import) ajax 函数
import { ajax } from 'utils';
var query = 'Rollup';
// 调用 ajax 函数
ajax( 'https://api.example.com?search=' + query ).then( handleResponse );
因为 Rollup 只引入最基本最精简代码,所以可以生成轻量、快速,以及低复杂度的 library 和应用程序。因为这种基于显式的 import
和 export
语句的方式,它远比「在编译后的输出代码中,简单地运行自动 minifier 检测未使用的变量」更有效。
兼容性(Compatibility)#
导入 CommonJS(Importing CommonJS)#
Rollup 可以通过插件导入已存在的 CommonJS 模块。
发布 ES6 模块(Publishing ES6 Modules)#
为了确保你的 ES6 模块可以直接与「运行在 CommonJS(例如 Node.js 和 webpack)中的工具(tool)」使用,你可以使用 Rollup 编译为 UMD 或 CommonJS 格式,然后在 package.json
文件的 main
属性中指向当前编译的版本。如果你的 package.json
也具有 module
字段,像 Rollup 和 webpack 2 这样的 ES6 感知工具(ES6-aware tools)将会直接导入 ES6 模块版本。
如何使用rollup
以cocos-vue2.x-vuex-template插件案例为例子。
在rollup.config.js中可以定义打包的入口以及使用各种插件来加强打包的功能,如导入.vue文件,导入文件转成 url。
可以看到该脚本是通过 Package.json
中获得多个打包的入口,Rollup
通过 Tree-shaking
解析 import
语法将导入的文件合并到入口,如图所示。
我们来看看配置文件的内容。
import VuePlugin from 'rollup-plugin-vue';
import commonjs from '@rollup/plugin-commonjs';
import postcss from 'rollup-plugin-postcss';
import babel from '@rollup/plugin-babel';
import path from 'path';
import typescript from 'rollup-plugin-typescript2';
import { terser } from 'rollup-plugin-terser';
import json from '@rollup/plugin-json';
import url from '@rollup/plugin-url';
import del from 'rollup-plugin-delete';
import { join, relative, dirname } from 'path';
import node from '@rollup/plugin-node-resolve';
import tsConfig from './tsconfig.json';
import replace from '@rollup/plugin-replace';
const PKGJSON = require('./package.json');
const OUTPUT_DIR = tsConfig.compilerOptions.outDir;
const ROOT_DIR = tsConfig.compilerOptions.rootDir;
const NORMALIZER_PATH = join(__dirname, 'rollup-utils', 'normalize-component.js').replace(/\\/g, '/');
const STYLE_INJECTOR_PATH = join(__dirname, 'rollup-utils', 'style-injector.js').replace(/\\/g, '/');
// #region input
/**
* @returns {Object.<string,string>}
*/
function getRollupInput() {
const input = {};
function assignToInput(path) {
if (typeof path === 'string') {
const key = relative(OUTPUT_DIR, path).replace('.js', '');
const value = join(ROOT_DIR, relative(OUTPUT_DIR, path).replace('js', 'ts'));
input[key] = value;
}
}
const panels = PKGJSON.panels;
if (panels) {
for (const key in panels) {
if (Object.hasOwnProperty.call(panels, key)) {
const element = panels[key];
const main = element.main;
if (main) {
assignToInput(main);
}
}
}
}
const main = PKGJSON.main;
if (main) {
assignToInput(main);
}
const contributions = PKGJSON.contributions;
if (contributions) {
const scene = PKGJSON.contributions.scene;
if (scene) {
const script = scene.script;
if (script) {
assignToInput(script);
}
}
const builder = PKGJSON.contributions.builder;
if (builder) {
assignToInput(builder);
}
const preferences = PKGJSON.contributions.preferences;
if (preferences) {
const custom = preferences.custom;
if (custom) {
assignToInput(custom);
}
}
const project = PKGJSON.contributions.project;
if (project) {
const custom = project.custom;
if (custom) {
assignToInput(custom);
}
}
}
return input;
}
// #endregion
const INPUT = getRollupInput();
// #region Plugins setting
// ** READ THIS ** before editing `knownAssetTypes`.
// If you add an asset to `knownAssetTypes`, make sure to also add it
// to the TypeScript declaration file `src/shims.d.ts`.
export const KNOWN_ASSET_TYPES = [
// images
'png',
'jpe?g',
'gif',
'svg',
'ico',
'webp',
'avif',
// media
'mp4',
'webm',
'ogg',
'mp3',
'wav',
'flac',
'aac',
// fonts
'woff2?',
'eot',
'ttf',
'otf',
// other
'wasm',
'webmanifest',
'pdf',
'txt',
];
const DEFAULT_ASSETS_REG_EXP = new RegExp(
'\\.(' + KNOWN_ASSET_TYPES.join('|') + ')(\\?.*)?$'
);
const VUEPLUGIN = VuePlugin({
defaultLang: { script: 'ts' },
/**
* @en Inject style
* @zh 注入样式
*/
css: true,
/**
* @en Injector style to shadow dom
* @zh 往shadow dom 注入样式
*/
isWebComponent: true,
/**
* @en This script describes which node to inject style into
* @zh 这个脚本描述了往哪个节点注入样式
*/
normalizer: `~${NORMALIZER_PATH}`,
/**
* @en This script describes how to inject style
* @zh 这个脚本描述了如何注入样式
*/
styleInjectorShadow: `~${STYLE_INJECTOR_PATH}`,
});
/**
* @en Set the value of "import.meta.relativePath" to the relativePath from the directory where "bundle" is located to the root directory
* @zh 设置 import.meta.relativePath 的值为 bundle 所在目录到根目录的相对路径
* @returns
*/
function RelativeRoot() {
return {
name: 'relative-root', // this name will show up in warnings and errors
resolveImportMeta(property, options) {
if (property === 'relativePath') {
return `"${relative(dirname(join(OUTPUT_DIR, options.chunkId)), '').replace(/\\/g, '/')}"`;
}
return null;
},
};
}
/**
* @en Parse typescript
* @zh 解析 typescript
*/
const TYPESCRIPT = typescript({
tsconfig: path.resolve('tsconfig.json'),
});
const NODE = node({});
const COMMONJS = commonjs();
/**
* @en Parsing styles so that styles can be imported
* @zh 解析样式文件,可以导入样式
*/
const POSTCSS = postcss({
inject: false,
extract: false,
});
/**
* @en parse json
* @zh 解析 json
*/
const JSON_Plugin = json();
/**
* A Rollup plugin which imports files as data-URIs or ES Modules.
* @see https://www.npmjs.com/package/@rollup/plugin-url
*/
const URL = url({
limit: 0,
fileName: '[dirname][name][extname]',
destDir: OUTPUT_DIR,
publicPath: OUTPUT_DIR + '/',
sourceDir: path.join(__dirname, ROOT_DIR),
include: DEFAULT_ASSETS_REG_EXP,
});
/**
* @en delete output dir once before compilation
* @zh 编译前清空输出目录
*/
const DEL_ONCE = del({
'targets': OUTPUT_DIR + '/*',
runOnce: true,
});
const REPLACE = replace({
'process.env.NODE_ENV': JSON.stringify('production'),
});
const RELATIVE_ROOT = RelativeRoot();
/**
* @en ES6 syntax => ES5 syntax
* @zh 将 ES6 格式转换为 ES5 格式
*/
const BABEL = babel();
// #endregion
/**
* @type {import('rollup').RollupOptions['plugins']}
*/
const COMMON_PLUGINS = [
VUEPLUGIN, TYPESCRIPT, COMMONJS, POSTCSS, BABEL, URL, JSON_Plugin, RELATIVE_ROOT, DEL_ONCE, NODE,
];
/**
*
* @param {string} id
* @en Declare external modules
* @zh 声明外部模块
*/
function external(id) {
}
/**
* @type {import('rollup').RollupOptions}
*/
const DEBUG_CONFIG = {
input: INPUT,
output: {
format: 'cjs',
dir: path.resolve(__dirname, OUTPUT_DIR),
},
plugins: [...COMMON_PLUGINS],
external,
};
/**
* @type {import('rollup').RollupOptions}
*/
const RELEASE_CONFIG = {
input: INPUT,
output: {
format: 'cjs',
dir: path.join(__dirname, OUTPUT_DIR),
},
plugins: [
...COMMON_PLUGINS,
/**
* @en uglify scripts
* @zh 代码混淆
*/
terser(),
REPLACE,
],
external,
};
export default (commandLineArgs) => {
if (commandLineArgs.configDebug === true) {
return DEBUG_CONFIG;
}
return RELEASE_CONFIG;
};
这里有两种不同的打包配置分别对应着开发版本以及发布版本,通过命令行判断使用哪种配置。
以下表格简单说明了插件的作用。
功能 | 使用的插件 | dev(开发) | release(发布) |
---|---|---|---|
解析 vue 文件 |
rollup-plugin-vue | √ | √ |
将 CommonJS 转换成 ES2015 模块 |
rollup-plugin-commonjs | √ | √ |
允许导入 css\less\sass 文件,导入后解析为 css 字符串 | rollup-plugin-postcss | √ | √ |
ES6 语法=> ES5 语法 |
@rollup/plugin-babel | √ | √ |
解析 typescript
|
rollup-plugin-typescript2 | √ | √ |
解析 json 文件 | @rollup/plugin-json | √ | √ |
允许导入指定类型的资源文件,当前配置的效果是,导入资源后返回资源相对于扩展根目录的路径,并且将文件拷贝到 dist 目录下的对应文件 | @rollup/plugin-url | √ | √ |
编译前删除 dist 目录 | rollup-plugin-delete | √ | √ |
A Rollup plugin which locates modules using the Node resolution algorithm, for using third party modules in node_modules
|
@rollup/plugin-node-resolve | √ | √ |
混淆脚本 | rollup-plugin-terser | √ | |
替换 ‘process.env.NODE_ENV’ 为 ‘production’ 使得 vue 启用生产环境模式 | @rollup/plugin-replace | √ |
原理说明
设置打包入口
在 rollup.config.js
中使用了 getRollupInput
扫描 package.json
常见的脚本来获取打包的入口,
可以这么设置 rollup 打包的入口。
const input = {
"main":"src/main.ts"
}
/**
* @type {import('rollup').RollupOptions}
*/
const config ={
input,
output: {
format: 'cjs',
dir: path.resolve(__dirname, './dist'),
}
}
最终 src/main.ts
将被打包到 dist/main.js
如果有需要别的打包入口,可以自行设置配置中的 input。
Vue文件样式注入原理
creator的面板使用了 shadow dom
而 rollup-plugin-vue 默认往 header 注入样式,这并不能满足我们的需求。
所以我们需要在 rollup-plugin-vue 插件进行配置
const VUEPLUGIN = VuePlugin({
defaultLang: { script: 'ts' },
/**
* @en Inject style
* @zh 注入样式
*/
css: true,
/**
* @en Injector style to shadow dom
* @zh 往shadow dom 注入样式
*/
isWebComponent: true,
/**
* @en This script describes which node to inject style into
* @zh 这个脚本描述了往哪个节点注入样式
*/
normalizer: `~${NORMALIZER_PATH}`,
/**
* @en This script describes how to inject style
* @zh 这个脚本描述了如何注入样式
*/
styleInjectorShadow: `~${STYLE_INJECTOR_PATH}`,
});
/// rollup-utils/normalize-component.js
.... line 52 ....
} else if (style) {
hook = shadowMode ? function(context) {
// 我们将样式注入到了 vue 实例的 $root.$el 的父节点中
style.call(this, createInjectorShadow(context, this.$root.$el.parentNode));
} : function(context) {
style.call(this, createInjector(context));
};
}
/// rollup-utils/style-injector.js
// 此处暴露的方法对应着 creatorInjectorShadow ,我们将在 shadowRoot 节点下生成样式节点
module.exports = function(context, shadowRoot) {
...
使用相对路径获取文件绝对路径的原理
rollup 后脚本将会被合并到入口中,这代表使用 __dirname
以及 __filename
是不可靠的,我们需要注意这点,那么问题来了,如果需要扩展中读写文件,而相对路径是不可靠的那应该如何操作呢?
首先要解决的是拿到扩展的根目录
/**
* @en Set the value of "import.meta.relativePath" to the relativePath from the directory where "bundle" is located to the root directory
* @zh 设置 import.meta.relativePath 的值为 bundle 所在目录到根目录的相对路径
* @returns
*/
function RelativeRoot() {
return {
name: 'relative-root', // this name will show up in warnings and errors
resolveImportMeta(property, options) {
if (property === 'relativePath') {
return `"${relative(dirname(join(OUTPUT_DIR, options.chunkId)), '').replace(/\\/g, '/')}"`;
}
return null;
},
};
}
我们使用这个 Rollup 插件后,在源码中能够通过 import.meta.relativePath
获取打包后的 bundle 到扩展的相对路径。
const rootPath = join(__dirname, import.meta.relativePath)
通过以上方式即可获取扩展的根目录的绝对路径。
这里提供了两种解决方案读写文件
假设 tsConfig.compilerOptions.rootDir
为 ./src
假设 tsConfig.compilerOptions.outputDir
为 ./dist
1、使用 @rollup/plugin-url 插件 ,把文件资源存放在我们的 tsConfig.compilerOptions.rootDir
指定的目录下。
例如希望能够在插件中读写某个 txt 文件,然后配置 rollup.config.js
/// rollup.config.js
const KNOWN_ASSET_TYPES = ['txt']
const DEFAULT_ASSETS_REG_EXP = new RegExp(
'\\.(' + KNOWN_ASSET_TYPES.join('|') + ')(\\?.*)?$'
);
/**
* A Rollup plugin which imports files as data-URIs or ES Modules.
* @see https://www.npmjs.com/package/@rollup/plugin-url
*/
const URL = url({
// 导入任意大小的资源都返回相对路径,并且将文件拷贝到对应的 destDir 中
limit: 0,
fileName: '[dirname][name][extname]',
destDir: tsConfig.compilerOptions.outDir,
publicPath: tsConfig.compilerOptions.outDir + '/',
sourceDir: path.join(__dirname, tsConfig.compilerOptions.rootDir),
include: DEFAULT_ASSETS_REG_EXP,
});
在 shims.d.ts
声明 txt 模块,以免报错。
/// shims.d.ts
declare module '*.txt'
{
export default string;
}
然后我们通过以下代码可以获取 txt 文件的绝对路径。
/// src/main.ts
/*
* @zh 从扩展根目录到文件的路径
* @en relative path from extension's root directory to file
*/
import relativePathFromRootToTXT from './my-number.txt';
import { join } from 'path';
/**
* @zh 获取扩展的根目录的绝对路径
* @en Gets the absolute path of the extension's root directory
*/
const rootPath = join(__dirname, import.meta.relativePath);
/**
* @zh txt 文件的绝对路径
* @en absolute path of txt file
*/
const filePath = join(rootPath, relativePathFromRootToTXT);
2、把文件资源存放在扩展的目录下,不放在 tsConfig.compilerOptions.rootDir
或tsConfig.compilerOptions.outputDir
目录下
例如我们扩展的目录结构如下
my-extension
-
src
-
dist
-
public-path
将文件资源存放在 public-path 中,然后再脚本中通过以下方式获取文件资源的绝对路径。
/// src/main.ts
import { join } from 'path';
/**
* @zh 获取扩展的根目录的绝对路径
* @en Gets the absolute path of the extension's root directory
*/
const rootPath = join(__dirname, import.meta.relativePath);
/**
* @zh txt 文件的绝对路径
* @en absolute path of txt file
*/
const filePath = join(rootPath, 'public-path', 'my-number.txt');
压缩扩展包需要注意的事
rollup 已将 node_modules 引用到的第三方库整合进了源码中,如果没有声明某个包为 external ,是不需要将 node_modules 带入压缩包中的。
3.3 插件示例
rollup-vue2.zip (190.1 KB)