Webpack 5以及其他打包工具

webpack5

模块联邦

https://www.kelen.cc/posts/webpack5-module-federation

Webpack5 模块联邦让 Webpack 达到了线上 Runtime 的效果,让代码直接在项目间利用 CDN 直接共享,不再需要本地安装 Npm 包、构建再发布了

Webpack 可以通过 DLL 或者 Externals 做代码共享时 Common Chunk,但不同应用和项目间这个任务就变得困难了,我们几乎无法在项目之间做到按需热插拔。

常见共享模块的场景

UMD 模块

微前端:多个项目共存于一个页面,有点类似iframe,共享的对象是项目级的,页面级的 子应用间的chunk以及对象可通过全局事件共享,但是公共包在项目安置以及打包编译很难放

webpack的方案是直接将一个应用的 bundle,应用于另一个应用,动态分发 runtime 子模块给其他应用。

使用方式,先设置本模块可以被导入

const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

module.exports = {
  // other webpack configs...
  plugins: [
    new ModuleFederationPlugin({
      // 1. name 当前应用名称,需要全局唯一
      name: "app_one_remote",
      // 2. remotes 可以将其他项目的 name 映射到当前项目中
      remotes: {
        app_two: "app_two_remote",
        app_three: "app_three_remote"
      },
      // 3. exposes 表示导出的模块,只有在此申明的模块才可以作为远程依赖被使用
      exposes: {
        AppContainer: "./src/App"
      },
      // 4. shared可以让远程加载的模块对应依赖改为使用本地项目的 React或ReactDOM。
      shared: ["react", "react-dom", "react-router-dom"]
    }),
    new HtmlWebpackPlugin({
      template: "./public/index.html",
      chunks: ["main"]
    })
  ]
};

导入使用

import { Search } from "app_two/Search";
app_two/Search来自于app_two 的配置:

// app_two的webpack 配置
export default {
  plugins: [
    new ModuleFederationPlugin({
      name: "app_two",
      library: { type: "var", name: "app_two" },
      filename: "remoteEntry.js",
      exposes: {
        Search: "./src/Search"
      },
      shared: ["react", "react-dom"]
    })
  ]
};

模块联邦本身是一个普通的 Webpack 插件 ModuleFederationPlugin,插件有几个重要参数:

  1. name 当前应用名称,需要全局唯一。
  2. filename :必须,暴露的文件名,在使用者通过 remotes 来引入
  3. library :可选, umd的名称,类似于jQuery的$,lodash的_
  4. remotes 可以将其他项目的 name 映射到当前项目中。
  5. exposes 表示导出的模块,只有在此申明的模块才可以作为远程依赖被使用。
  6. shared 是非常重要的参数,制定了这个参数,可以让远程加载的模块对应依赖改为使用本地项目的 React 或 ReactDOM。

原理

ModuleFederationPlugin 组合了 ContainerPluginContainerReferencePlugin

ContainerPlugin该插件使用指定的公开模块来创建一个额外的容器入口。

ContainerReferencePlugin该插件将特定的引用添加到作为外部资源(externals)的容器中,并允许从这些容器中导入远程模块。它还会调用这些容器的 override API 来为它们提供重载。本地的重载(当构建也是一个容器时,通过 __webpack_override__override API)和指定的重载被提供给所有引用的容器。

  • 它既可以暴露,又可以使用 webpack 支持的任何模块类型
  • 代码块加载应该并行加载所需的所有内容(web:到服务器的单次往返)
  • 从使用者到容器的控制

    • 重写模块是一种单向操作
    • 同级容器不能重写彼此的模块。
  • 概念适用于独立于环境

    • 可用于 web、Node.js 等
  • 共享中的相对和绝对请求

    • 会一直提供,即使不使用
    • 会将相对路径解析到 config.context
    • 默认不会使用 requiredVersion
  • 共享中的模块请求

    • 只在使用时提供
    • 会匹配构建中所有使用的相等模块请求
    • 将提供所有匹配模块
    • 将从图中这个位置的 package.json 提取 requiredVersion
    • 当你有嵌套的 node_modules 时,可以提供和使用多个不同的版本
  • 共享中尾部带有 / 的模块请求将匹配所有具有这个前缀的模块请求

一般来说,remote 是使用 URL 配置的,但是你也可以向 remote 传递一个 promise,其会在运行时被调用。你应该用任何符合上面描述的 get/init 接口的模块来调用这个 promise。例如,如果你想传递你应该使用哪个版本的联邦模块,你可以通过一个查询参数做以下事情

构建速度优化

缓存

通常我们会借助一些其他的配置优化缓存

  1. cache-loader,针对一些耗时的工作进行缓存。比如缓存babel-loader的工作。
  2. terser-webpack-plugin 或 uglifyjs-webpack-plugin的cache以及parallel。terserPlugin继承自uglifyjsPlugin,我们可以开启插件的cache以及parallel特性来加快压缩。(默认开启)
// 对babel-loader的工作进行缓存
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['cache-loader', 'babel-loader'],
        include: path.resolve('src'),
      },
    ],
  },
};
optimization: {
  minimizer: [
    new TerserPlugin({
      cache: true,   // 开启该插件的缓存,默认缓存到node_modules/.cache中
      parallel: true,  // 开启“多线程”,提高压缩效率
      exclude: /node_modules/
    })
  ],
},

而在webpack5中可以使用cache 特性来将webpack工作缓存到硬盘中。存放的路径为node_modules/.cache/webpack

module.exports = {
  //开发环境默认值为 cache.type = "memory"。
  //生产环境可手动设为 cache.type = "filesystem"。
  cache: {
    type: 'filesystem',
    version: 'your_version'
  }
};

持久化缓存

在日常开发中我们会尽量减少文件hash发生变化的情况,以最大化的利用缓存,节省流量。这就是我们常说的“优化持久化缓存”。首先最简单的措施就是使用contenthash来作为文件哈希后缀,只有当文件内容发生变化的时候,哈希才会发生改变。

在webpack4中,当我们新增一个模块或者一个入口,所有文件的哈希后缀都发生了改变,但这不符合期望,原有文件hash不应发生变化

在webpack4 中,chunkId与moduleId都是自增id。也就是只要我们新增一个模块,那么代码中module的数量就会发生变化,从而导致moduleId发生变化,于是文件内容就发生了变化。chunkId也是如此,新增一个入口的时候,chunk数量的变化造成了chunkId的变化,导致了文件内容变化。

webpack4可以通过设置optimization.moduleIds = 'hashed'与optimization.namedChunks=true来解决这些问题,但都有性能损耗等副作用。

而webpack5 在production模式下optimization.chunkIds和optimization.moduleIds默认会设为'deterministic',webpack会采用新的算法来计算确定性的chunkI和moduleId。

tree shaking优化

1: 对于模块引入嵌套场景,如嵌套后未引用则可以shaking掉

// inner.js
export const a = 1;
export const b = 2;

// module.js
import * as inner from "./inner";
export { inner }

// user.js
import * as module from "./module";
console.log(module.inner.a);

2.内部模块。Webpack 4 不会去分析导入和导出模块之间的依赖关系,Webpack5 里面会通过 optimization.innerGraph记录依赖关系。比如下面这个例子,只有 test 方法使用了 someting 。最终可以实现标记更多没有使用的导出项

import { something } from "./something";

function usingSomething() {
	return something;
}

export function test() {
	return usingSomething();
}

3.commonjs tree shaking支持。Webpack不仅仅支持 ES module 的 tree Shaking,commonjs规范的模块开始支持了

4.sideEffect关键字

webpack 4 添加了 sideEffects , 在 package.json 中添加这个属性, 表示, 哪些模块是 纯的 ES6 模块, 当代码未被使用, webpack 可以被删除

基于 ESM 模块的项目, 很轻松的能分析到, 哪些文件是未使用的, 可以轻松 树摇掉的, 但是项目中没办法达到这种纯度, 所以,可以通过如下的方式,告知 webpack 怎么处理未使用的代码

可以直接在 package.json 中配置 sideEffects 为 false ,告知 webpack 所有代码都没有副作用, 可以安全的删除

{
  "name": "webpack 测试sideEffects 删除未使用的代码",
  "sideEffects": [
     "./src/some-side-effectful-file.js",
    "/src/main.js",
    "/\.css$/"
  ]
}
{
  "name": "webpack 测试sideEffects 删除未使用的代码",
  "sideEffects": false
}

包体积优化

在webpack5之前,webpack会自动的帮我们项目引入Node全局模块polyfill。我们可以通过node配置

但是webpack团队认为,现在大多数工具包多是为前端用途而编写的,所以不再自动引入polyfill。我们需要自行判断是否需要引入polyfill,当我们用weback5打包的时候,webpack会给提示

svgo

SVG 优化器(SVGO)是一个流行的开源工具,用于压缩 SVG 文件。它的工作原理是 "安全地删除编辑元数据、注释、隐藏元素,[以及] 默认或非最佳值"。从 Github 的依赖性数字来看,SVGO 是一个相当广泛使用的工具,被用于 460 万个存储库。作为参考, React 在 700 万个存储库中使用。

安装

npm install -g svgo

使用

svgo [OPTIONS] [ARGS]

## 
svgo E:\a.svg -o  E:\b.svg

Assets Module

Webpack5 为我们提供了资源模块,用一种更简单、更方便的方式来替换原来的raw-loader、url-loader、file-loader等图文、字体文件资源

Webpack5 为我们提供了 asset/resourceasset/inlineasset/sourceasset 四种资源类型,具体介绍如下所示

asset/resource:将资源文件输出到指定的输出目录,作用等同于 file—loader

asset/inline:将资源文件内容以指定的格式进行编码(一般为 base64),然后以 data URI 的形式嵌入到生成的 bundle 中,作用等同于 url-loader

asset/source:将资源文件的内容以字符串的形式嵌入到生成的 bundle 中,作用相当于 raw-loader

asset:作用等同于设置了 limit 属性的 url-loader,即资源文件的大小如果小于 limit 的值(默认值为 8kb),则采用 asset/inline 模式,否则采用 asset/resource 模式

资源模块的配置

原来使用file-loader如下

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: [
          {
            loader: 'file-loader',
          },
        ],
      },
    ],
  },
};

调整配置

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        type: 'asset' // 可用值:asset/resource、asset/inline、asset/source、asset
      },
    ],
  },
};

默认情况下,在处理 resource 类型的资源时,相关资源会被输出到 output.path 目录下(文件命名规则为 [hash][ext][query]),我们可通过设置 output.assetModuleFilename 来改变其输出目录

// webpack.config.js
module.exports = {
  output: {
    path: path.resolve(__dirname, 'dist'),
    assetModuleFilename: './static/images/[hash][ext][query]',
  },
};

这里需要注意的是,assetModuleFilename 的值须为相对路径,如值为绝对路径

其他特性

  • Webpack 生成的代码不再仅仅是ES5,也会生成 ES6 的代码
  • Node.js 的最小支持版本从 6 升级到了 10
  • 新的 Web 平台支持。在Webpack 5 里面开始原生支持 JSON Modules、Asset Modules、Native Worker 和 异步模块等等

js底层原理

js执行过程

JavaScript是一种解释型语言,执行时按照解释器-编译器-执行的顺序进行。

解释器:

JavaScript的解释器包含在即时编译器(JIT,just in time)中,在一句一句编译源代码的同时又会将编译后的一些代码保存下来。

即时编译器中首先有监视器监控着代码的运行状况。如果一段代码运行了几次,那么这段代码就会被标记为warm代码,标记为warm的代码会被编译器送到基线编译器编译,同时把结果缓存起来,如果一个函数被基线编译器编译,那么函数的每一行都会被编译成一个表,这个表的索引是行号和变量类型,如果监视器发现有代码使用相同的变量类型命中这段代码时,JIT会直接提取这段代码编译后的内容。

如果标记为warm的代码还在不断被调用,那么它会被标记为hot,标记为hot的代码,意味着可能会被经常调用,所以可能需要更多时间优化编译结果。监视器会把该段代码送到优化编译器,编译成一个高效的版本并存储下来。

编译:

parse阶段-

词法分析和语法分析:词法分析是把需要执行的代码字符串分割出来,生成一系列token,便于做语法分析

语法分析分析上述输出,输出AST抽象语法树,如果分析发现异常则抛出错误。

Ignition:Ignition是v8的解释器,它会根据抽象语法树生成对应的字节码并执行。

所谓字节码,就是机器码的抽象,机器码虽然运行效率最高,但是占用空间较大,导致十分占内存,所以以时间换空间,引入字节码

Turbofan:Turbofan能够把字节码翻译成机器码,当发现hot代码时,turbofan会把字节码翻译成机器码,然后调用生成的机器码。如果发现假设失效,则退回字节码

Orinoco:Orinoco是v8的垃圾回收模块

执行:

执行阶段有执行上下文和执行栈两个概念

执行上下文就是JavaScript的运行环境,通常有

全局执行上下文,即window对象

函数执行上下文,即函数执行的时候被创建,每次调用函数就会创建一个新的执行上下文

eval执行上下文

对于每个执行上下文创建时,都有三个比较重要的属性:

变量对象:表示执行上下文的数据作用域,

作用域链

确定this指向:

执行栈:

js通过执行栈管理多个执行上下文,执行栈最底端是全局上下文,全局上下文中执行函数的时候,相应上下文会入栈,其中不断调用函数,不同的函数不断创建不同的执行上下文,直到函数执行完毕,上下文出栈,即后进先出

如果有异步函数,在执行栈的空的时候查看任务队列,微任务优先入栈。

AST的应用

Es-lint就是使用ast查询器查询AST,AST选择器类似CSS选择器,每个ESlint规则都是通过选择器操作的

Babel:Babel对于一段代码的工作流程是:

1.输入代码

2.词法分析,将代码分成token

3.语法分析:把token转换成AST

4.遍历AST

5.改变AST,增删改查。

6.AST转换为源代码

webpack:webpack主要通过遍历AST分析出模块的依赖、消除无用代码等

Gulp

Gulp基于Node.js的前端构建工具,通过Gulp的插件可以实现前端代码的编译(sass、less)、压缩、测试;图片的压缩;浏览器自动刷新,还有许多强大的插件可以在这里查找。

安装

 npm install gulp -g

Gulp Api

使用gulp,仅需知道4个API即可:gulp.task(),gulp.src(),gulp.dest(),gulp.watch()

gulp.src()方法正是用来获取流的,但要注意这个流里的内容不是原始的文件流,而是一个虚拟文件对象流(Vinyl files),这个虚拟文件对象中存储着原始文件的路径、文件名、内容等信息

gulp.dest()方法是用来写文件的,其语法为:

gulp的使用流程一般是这样子的:首先通过gulp.src()方法获取到我们想要处理的文件流,然后把文件流通过pipe方法导入到gulp的插件中,最后把经过插件处理后的流再通过pipe方法导入到gulp.dest()中,gulp.dest()方法则把流中的内容写入到文件中,

gulp.task方法用来定义任务,内部使用的是Orchestrator,其语法为:

gulp.watch()用来监视文件的变化,当文件发生变化后,我们可以利用它来执行相应的任务,例如文件压缩等。其语法为

常用插件

安装gulp插件

CSS

js

html

图片、资源

其他

安装这些插件

npm install gulp-ruby-sass gulp-autoprefixer gulp-minify-css gulp-jshint gulp-concat gulp-uglify gulp-imagemin gulp-notify gulp-rename gulp-livereload gulp-cache del --save-dev

创建一个gulpfile.js在根目录下,然后在里面加载插件:

var gulp = require('gulp'),
    sass = require('gulp-ruby-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    minifycss = require('gulp-minify-css'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify'),
    cache = require('gulp-cache'),
    livereload = require('gulp-livereload'),
    del = require('del');

创建任务

// Load plugins
var gulp = require('gulp'),
    sass = require('gulp-ruby-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    minifycss = require('gulp-minify-css'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    rename = require('gulp-rename'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify'),
    cache = require('gulp-cache'),
    livereload = require('gulp-livereload'),
    del = require('del');
// Styles,编译sass,添加前缀,保存到我们指定的目录下面,还没结束,我们还要压缩,给文件添加.min后缀再输出压缩文件到指定目录,最后提醒任务完成了
gulp.task('styles', function() {
  return gulp.src('src/styles/main.scss')
    .pipe(sass({ style: 'expanded', }))
    .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
    .pipe(gulp.dest('dist/styles'))
    .pipe(rename({ suffix: '.min' }))
    .pipe(minifycss())
    .pipe(gulp.dest('dist/styles'))
    .pipe(notify({ message: 'Styles task complete' }));
});
// Scripts,js代码校验、合并和压缩
gulp.task('scripts', function() {
  return gulp.src('src/scripts/**/*.js')
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('default'))
    .pipe(concat('main.js'))
    .pipe(gulp.dest('dist/scripts'))
    .pipe(rename({ suffix: '.min' }))
    .pipe(uglify())
    .pipe(gulp.dest('dist/scripts'))
    .pipe(notify({ message: 'Scripts task complete' }));
});
// Images,使用imagemin插件把所有在src/images/目录以及其子目录下的所有图片(文件)进行压缩,我们可以进一步优化,利用缓存保存已经压缩过的图片
gulp.task('images', function() {
  return gulp.src('src/images/**/*')
    .pipe(cache(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true })))
    .pipe(gulp.dest('dist/images'))
    .pipe(notify({ message: 'Images task complete' }));
});
// Clean,在任务执行前,最好先清除之前生成的文件
gulp.task('clean', function(cb) {
    del(['dist/assets/css', 'dist/assets/js', 'dist/assets/img'], cb)
});
// Default task设置默认任务,在命令行下输入$ gulp执行的就是默认任务,现在我们为默认任务指定执行上面写好的三个任务:
gulp.task('default', ['clean'], function() {
    gulp.start('styles', 'scripts', 'images');
});
// Watch,实现当文件修改时自动刷新页面,我们要修改watch任务,配置LiveReload:
gulp.task('watch', function() {
  // Watch .scss files
  gulp.watch('src/styles/**/*.scss', ['styles']);
  // Watch .js files
  gulp.watch('src/scripts/**/*.js', ['scripts']);
  // Watch image files
  gulp.watch('src/images/**/*', ['images']);
  // Create LiveReload server
  livereload.listen();
  // Watch any files in dist/, reload on change
  gulp.watch(['dist/**']).on('change', livereload.changed);
});

其他插件

打包前拷贝到dist目录,之后所有的操作基于dist目录

const { series, src, dest } = require('gulp');
const clean = require('gulp-clean');
//清除文件夹插件
function clear() {
	return src('dist',{ allowEmpty: true }).pipe(clean());
}
//拷贝静态资源
function assets() {
  return src('src/assets/**/*.*').pipe(dest('dist/assets/'));
}
//拷贝css文件
function css() {
  return src(['src/css/**/*.css']).pipe(dest('dist/css'));
}
//拷贝js文件
function js() {
  return src(['src/js/**/*.js']).pipe(dest('dist/js'))
}
//拷贝html文件
function html() {
  return src('src/views/**/*.html').pipe(dest('dist/views'))
}

exports.build = series(
	clear,
  assets,
  css,
  js,
  html
)

给打包文件加hash

npm i gulp-dev -D

在gulpfile。js中添加以下代码

const rev = require('gulp-rev');
//给css文件加入hash
function cssRevHash() {
  return src('dist/css/**/*.css')
  	.pipe(rev())
  	.pipe(dest('/dist/css'))
  	.pipe(rev.manifest())
  	.pipe(dev('rev/css'));
}
//给js文件加入hash
function jsRevHash() {
  return src('dist/js/**/*.js')
  	.pipe(rev())
  	.pipe(dest('dist/js/'))
  	.pipe(rev.manifest())
  	.pipe(dev('rev/js'));
}

export.build = series(
		...
  	cssRevHash,
  	jsRevHash
)

替换文件中的源文件

npm i gulp-rev-collector -D

在gulpfilejs中加入代码

const revCollector = require('gulp-rev-collector');

function htmlRevInject() {
  return src([`rev/${revDir}/**/*.json`,'dist/views/**/*.html'])
  	.pipe(revCollector({ replaceReved: true}))
  	.pipe(dest('dist/views'));
}

exports.build = series(
	...
  htmlRevInject
)

打包zip文件

npm i gulp-zip -D

在gulpfilejs中加入代码

const zip = require('gulp-zip')

function zipiupiu() {
  return src('dist/**/*').pipe(zip(zipName)).pipe(dest('dist'));
}

exports.build = series(
	...
  zipiupiu
)

动态处理打包环境,有prod和dev两种

npm i cross-env -D

在packagejson中添加命令

{
  "scripts": {
		"build:env": "cross-env NODE_ENV=development gulp build",
    "build:prod": "cross-env NODE_ENV=production gulp build"
  };
}

根据不同环境执行不同的打包方式,如开发环境不需要压缩。

if (process.env.NODE_ENV === 'production') {
	exports.build = series (
  	clear,
    assets,
    css,
    js,
    html,
    cssRevHash,
    jsRevHash,
    htmlRevInject,
    htmlMinify,
    zipiupiu
  )
}else {
  exports.build = series (
  	clear,
    assets,
    css,
    js,
    html,
    cssRevHash,
    jsRevHash,
    htmlRevInject,
    zipiupiu
  )
}

gulp与webpack、grant的区别

与webpack

gulp严格上讲,模块化不是他强调的东西,他旨在规范前端开发流程。

webpack更是明显强调模块化开发,而那些文件压缩合并、预处理等功能,不过是他附带的功能。

gulp应该与grunt比较,而webpack应该与browserify

gulp与webpack上是互补的,还是可替换的,取决于你项目的需求。如果只是个vue或react的单页应用,webpack也就够用;如果webpack某些功能使用起来麻烦甚至没有(雪碧图就没有),那就可以结合gulp一起用。

与grunt

Grunt主要是以文件为媒介来运行它的工作流的,比如在Grunt中执行完一项任务后,会把结果写入到一个临时文件中,然后可以在这个临时文件内容的基础上执行其它任务,执行完成后又把结果写入到临时文件中,然后又以这个为基础继续执行其它任务...就这样反复下去。而在Gulp中,使用的是Nodejs中的stream(流),首先获取到需要的stream,然后可以通过stream的pipe()方法把流导入到你想要的地方,比如Gulp的插件中,经过插件处理后的流又可以继续导入到其他插件中,当然也可以把流写入到文件中。所以Gulp是以stream为媒介的,它不需要频繁的生成临时文件,这也是Gulp的速度比Grunt快的一个原因。

https://markpop.github.io/2014/09/17/Gulp%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B/

https://www.cnblogs.com/2050/p/4198792.html

Rollup

应用场景: 项目(特别是类库)只有 js,而没有其他的静态资源文件,使用 webpack 就有点大才小用了,因为 webpack bundle 文件的体积略大,运行略慢,可读性略低。这时候 rollup就是一种不错的解决方案

Rollup 的好处

  • Tree Shaking: 自动移除未使用的代码, 输出更小的文件
  • Scope Hoisting: 所有模块构建在一个函数内, 执行效率更高
  • Config 文件支持通过 ESM 模块格式书写
  • 可以一次输出多种格式:IIFE, AMD, CJS, UMD, ESM
  • Development 与 production 版本: .js, .min.js
  • 文档精简

配置config文件

创建一个rollup.config.js文件

import json from '@rollup/plugin-json';
import terser from '@rollup/plugin-terser';

export default {
  input: 'src/main.js',
  output: [
    {
      file: 'bundle.js',
      format: 'cjs'
    },
    {
      file: 'bundle.min.js',
      format: 'iife',
      name: 'version',
      plugins: [terser()]
    }
  ],
  plugins: [json()]
};

基础插件

  • rollup-plugin-alias: 提供 modules 名称的 alias 和 reslove 功能.
  • rollup-plugin-babel: 提供 Babel 能力, 需要安装和配置 Babel
  • rollup-plugin-eslint: 提供 ESLint 能力, 需要安装和配置 ESLint
  • rollup-plugin-node-resolve: 解析 node_modules 中的模块
import resolve from '@rollup/plugin-node-resolve';

export default [
  {
      resolve({
        extensions: ['.ts', '.js'],
        preferBuiltins: false,
        mainFields: ['jsnext:main', 'jsnext', 'module', 'main'],
      }),
  }
]
  • rollup-plugin-commonjs: 转换 CJS -> ESM, 通常配合上面一个插件使用
  • rollup-plugin-replace: 类比 Webpack 的 DefinePlugin , 可在源码中通过 process.env.NODE_ENV 用于构建区分 Development 与 Production 环境.
  • rollup-plugin-filesize: 显示 bundle 文件大小
  • rollup-plugin-uglify: 压缩 bundle 文件
  • rollup-plugin-serve: 类比 webpack-dev-server, 提供静态服务器能力
  • Rollup-plugin-vue: 解析vue文件
  • rollup-plugin-json:可以让rollup从json文件中读取数据
  • Rollup-plugin-muliti-input: rollup多入口插件
import multiInput from 'rollup-plugin-multi-input';

export default [
  {
    input: [
      'miniprogram/**/*.ts',
    ],
    plugins: [
      multiInput(),
    ]
  }
]
  • rollup-plugin-esbuild: rollup rebuild插件
import esbuild from 'rollup-plugin-esbuild';

export default [
  {
    plugins: [
      esbuild({
        sourceMap: false,
        minify: !isDev,
        target: 'es2018',
        legalComments: 'none',
      }),
    ]
  }
]
  • Rollup-plugin-typescript2: rollup编译typescript项目
// rollup.config.js
import typescript from 'rollup-plugin-typescript2';

export default {
	input: './main.ts',

	plugins: [
		typescript(/*{ plugin options }*/)
	]
}

https://github.com/ezolenko/rollup-plugin-typescript2

开发插件

// rollup-plugin-my-example.js
export default function myExample () {
  return {
    name: 'my-example', // this name will show up in warnings and errors
    resolveId ( source ) {
      if (source === 'virtual-module') {
        return source; // this signals that rollup should not ask other plugins or check the file system to find this id
      }
      return null; // other ids should be handled as usually
    },
    load ( id ) {
      if (id === 'virtual-module') {
        return 'export default "This is virtual!"'; // the source code for "virtual-module"
      }
      return null; // other ids should be handled as usually
    }
  };
}

// rollup.config.js
import myExample from './rollup-plugin-my-example.js';
export default ({
  input: 'virtual-module', // resolved by our plugin
  plugins: [myExample()],
  output: [{
    file: 'bundle.js',
    format: 'es'
  }]
});

插件就是一个JavaScript函数,调用构建时的api。

插件的api可以有几种属性

同步还是异步的

并发的

load

resolveId

buildStart

buildEnd

options

resolveDynamicImport

transform

watchChange

与gulp配合使用

rollup返回promise,所以可以gulp中可以监听

const gulp = require('gulp');
const rollup = require('rollup');
const rollupTypescript = require('@rollup/plugin-typescript');

gulp.task('build', async function () {
  const bundle = await rollup.rollup({
    input: './src/main.ts',
    plugins: [rollupTypescript()]
  });

  await bundle.write({
    file: './dist/library.js',
    format: 'umd',
    name: 'library',
    sourcemap: true
  });
});

Grunt

安装cli工具

npm install -g grunt-cli

安装grunt及用到的插件

npm install gulp grunt-contrib-uglify --save-dev

创建Gruntfile.js文件

module.exports = function(grunt) {
    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        uglify: {
            options: {
                banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                src: 'src/*.js',
                dest: 'build/all.min.js'
            }
        }
    });
    // 加载包含 "uglify" 任务的插件。
    grunt.loadNpmTasks('grunt-contrib-uglify');
    // 默认被执行的任务列表。
    grunt.registerTask('default', ['uglify']);
};

大部分的Gruntfile.js都非常臃肿,有一大段一大段的嵌套配置结构,令人看起来很烦躁。这些冗余的Gruntfile.js文件其实维护起来并不是那么轻量的,主要体现在下面几个方面,

  1. 配置和运行分离
  2. 大部分插件的职责不单一,做了不仅一件事
  3. 配置项过多,做的事情越多,配置增长的就越快,且越不可控制
  4. 任务执行中会产生一些临时文件,较频繁的IO操作导致性能滞后

我们再来看看gulp中是如何解决这些问题的,

  1. gulp遵循code over configuration的原则,直接就在调用的地方配置
  2. gulp的插件严格遵循单一职责原则,一个插件仅作一件事,一个gulp一般只有20多行代码就搞定
  3. gulp基于流的思想,并没有过多的配置,任务更多的像是在写代码而不是定义配置
  4. 这个是grunt的致命伤,gulp的流式构建改变了底层的流程控制,不再频繁的去进行IO操作了

Parcel

webpack学习资源

wepack:https://www.kancloud.cn/sllyli/webpack/1242354

如果你觉得我的文章对你有帮助的话,希望可以推荐和交流一下。欢迎關注和 Star 本博客或者关注我的 Github