前端分享--前端项目工程化(一)(新增rollup)

1,259 阅读13分钟

Loader与Plugin

什么是loader?

对于webpack来说,默认只能打包处理JS文件,或者说JS模块,但是webpack作为模块打包工具,需要打包的模块肯定不仅仅只有JS模块,还有:图片模块,CSS模块等等;但是webpack默认是没有图片模块、CSS模块打包功能的,所以问题来了,webpack如何打包除了JS模块之外的其他模块哪?那就得用到第三方loader了,所以按照我们表面理解:loader就是帮助webpack打包那些webpack默认打包不了的模块的工具;

configureWebpack 和chainWebpack的作用相同,唯一的区别就是它们修改webpack配置的方式不同:
  • configureWebpack 通过操作对象的形式,来修改默认的webpack配置,该对象将会被 webpack-merge 合并入最终的 webpack 配置
  • chainWebpack 通过链式编程的形式,来修改默认的webpack配置

它允许我们更细粒度的控制其内部配置。接下来有一些常见的在 vue.config.js 中的 chainWebpack 修改的例子。

module.exports = {
  chainWebpack: config => {
    config.module
        .rule('expose1')
        .test(require.resolve('jquery'))
            .use()
            .loader('expose-loader')
            .options("jQuery")
            .end()
    config.module
        .rule('expose2')
        .test(require.resolve('jquery'))
            .use()
            .loader('expose-loader')
            .options("$")
            .end()
    config.module
      .rule("vue")
      .use("iview-loader") // 解决ivew组件 忽略前缀i的问题
      .loader("iview-loader")
      .options({
        prefix: false,
      })
      .end();
  }
}
module.exports = {
// 没有使用 iview-loader 时,必须使用 i-switch 标签。
  configureWebpack: (config) => {
    config.module.rules.push({
      test: /.vue$/,
      use: [{
        loader: 'iview-loader', // 解决ivew组件 忽略前缀i的问题
        options: {
          prefix: false
        }
      }]
    })
  },

}

如果说Loader负责文件转换,那么Plugin便是负责功能扩展。LoaderPlugin作为Webpack的两个重要组成部分,承担着两部分不同的职责。

以下的一些打包优化 都是Plugin

使用DllPlugin插件,优化提高打包时间

当我们使用一些社区的比较稳定的库的时候,比如react 比如vue,比如jquery 你会发现他几个月都不会更新一次,那么,我们就不需要重复打包了,只需要打包一次,下次打包只引用即可,那我应该怎么做呢,其实在webpack4的版本中,已经集成DllPlugin插件,我们只需要配置即可。 www.fengnayun.com/news/conten…

DLLPlugin vs SplitChunksPlugin

DLLPlugin主要是为了提升编译速度,把一些第三方库额外编译好,通过manifest.json文件映射引用关系,然后你开发的时候就不用再编译这些东西。至于CommonsChunkPluginSplitChunksPlugin是会编译出的代码做拆分。

webpack splitChunksPlugin,

首先webpack Bundle Analyzer Plugin打包分析工具可以看出哪些文件较大

用SplitChunks插件来控制Webpack打包生成的js文件的内容的精髓就在于,防止模块被重复打包,拆分过大的js文件,合并零散的js文件。最终的目的就是减少请求资源的大小和请求次数。 提取出来的通用 'echarts', 'moment', 'element-ui', 'xlsx'等使chunk

config.optimization.splitChunks({
  chunks: 'async',
  minSize: 1024 * 10, // 30000,
  maxSize: 0,
  minChunks: 1,
  maxAsyncRequests: 6,
  maxInitialRequests: 4,
  automaticNameDelimiter: '~',
  cacheGroups: {
      echarts: {
          name: 'echarts',
          test: /[\\/]node_modules[\\/]echarts[\\/]/,
          minSize: 0,
          minChunks: 1,
          reuseExistingChunk: true,
          chunks: 'all'
      },
      moment: {
          name: 'moment',
          test: /[\\/]node_modules[\\/]moment[\\/]/,
          minSize: 0,
          minChunks: 1,
          reuseExistingChunk: true,
          chunks: 'all'
      },
      'element-ui': {
          name: 'element-ui',
          test: /[\\/]node_modules[\\/]element-ui[\\/]/,
          minSize: 0,
          minChunks: 1,
          reuseExistingChunk: true,
          chunks: 'all'
      },
      xlsx: {
          name: 'xlsx',
          test: /[\\/]node_modules[\\/]xlsx[\\/]/,
          minSize: 0,
          minChunks: 1,
          reuseExistingChunk: true,
          chunks: 'all'
      },

      vendors: {
          name: 'chunk-vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
      },
      common: {
          name: 'chunk-common',
          minChunks: 2,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
      }
  }
});

ps:webpack4带来的最大优化便是对于懒加载块拆分的优化,删除了CommonsChunkPlugin,新增了优化后的SplitChunksPlugin
最初,chunks(以及内部导入的模块)是通过内部 webpack 图谱中的父子关系关联的。CommonsChunkPlugin 曾被用来避免他们之间的重复依赖,CommonsChunkPlugin的痛,痛在只能统一抽取模块到父模块,造成父模块过大,不易于优化
SplitChunksPlugin的好,好在解决了入口文件过大问题还能有效自动化的解决懒加载模块之间的代码重复问题

thread-loader

webpack4中官方文档的极力推荐thread-loader,并且HappyPack将不再被维护,所以当我们使用多进程打包时首选thread-loader thread-loader 使用起来也非常简单,只要把 thread-loader 放置在其他 loader 之前即可,这样一来,按照官方的解释之后的 loader 就会在一个单独的 worker 池(worker pool)中运行,并且还支持之定义配置,方便性能优化

 {
        test: /.js$/,
        exclude: /node_modules/,
        // 创建一个 js worker 池
        use: [ 
        //直接在loader之前使用
          'thread-loader',
          'babel-loader'
        ] 
      },
    //自定义配置行
    use[
    {
    loader: "thread-loader",
    // loaders with equal options will share worker pools
    // 设置同样option的loaders会共享
    options: {
      // worker的数量,默认是cpu核心数
      workers: 2,
      // 一个worker并行的job数量,默认为20
      workerParallelJobs: 50,
      // 添加额外的node js 参数
      workerNodeArgs: ['--max-old-space-size=1024'],
      // 允许重新生成一个dead work pool
      // 这个过程会降低整体编译速度
      // 开发环境应该设置为false
      poolRespawn: false,
      //空闲多少秒后,干掉work 进程
      // 默认是500ms
      // 当处于监听模式下,可以设置为无限大,让worker一直存在
      poolTimeout: 2000,
      // pool 分配给workder的job数量
      // 默认是200
      // 设置的越低效率会更低,但是job分布会更均匀
      poolParallelJobs: 50,
      }
    }
    'babel-loader'
    ]

路由懒加载优化

项目如果较大时,vue打包后的js文件也越来越大,这会是影响加载时间的重要因素。当构建的项目比较大的时候,懒加载可以分割代码块,提高页面的初始加载效率

优化方法:

当路由增多时,router中每个component都需要实现懒加载,但是在开发环境中使用懒加载,会导致代码更改的热跟新速度变慢,所以需要区分环境来使用路由的懒加载功能。

router文件夹下新建以下两个文件:

import_production.js:

module.exports = file => () => import('@/views/' + file + '.vue')

import_development.js:

module.exports = file => require('@/views/' + file + '.vue').default

然后在router/index.js中替换以下代码:

const _import = require('./import_' + process.env.NODE_ENV)
 
export default new Router({
  routes: [{ path: '/admin', name: '登陆', component: _import('admin') }]}) 

ps:常见的几种vue中路由懒加载

方法一 resolve 结合 AMD规范的require

这一种方法较常见。它主要是使用了resolve的异步机制,用require代替了import,实现按需加载,下面是代码示例:

**

import Vue from 'vue'
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
  routes: [
//     {
//       path: '/',
//       name: 'HelloWorld',
//       component: HelloWorld
//     }
        {
          path: '/',
          name: 'HelloWorld',
          component: resolve => require(['@/components/HelloWorld'], resolve)
        }
  ]
}) 
方法二 常见方法

vue-router在官网提供了一种方法,可以理解也是为通过Promiseresolve机制。因为Promise函数返回的Promiseresolve组件本身,而我们又可以使用import来导入组件。整合起来代码示例如下:

**

import Vue from 'vue'
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
  routes: [
//     {
//       path: '/',
//       name: 'HelloWorld',
//       component: HelloWorld
//     }
        {
          path: '/',
          name: 'HelloWorld',
          component: () => import('@/components/HelloWorld.vue')
        }
  ]
}) 

官网中还提供了通过注释语法来提供chunk name的方法,但是webpack的版本要高于2.4的版本。感兴趣的可以到官网了解一下。

组件按需引入(tree-shaking)

webpack2.0为了合理优化打包文件大小,引入了tree-shaking这一功能,借助于es6模块的静态解析 tree-shaking的实现才成为可能。 在webpack中,tree-shaking指的就是按需加载,即没有被引用的模块不会被打包进来,以减少包的大小。
ps:ES6 module 特点:

  • 只能作为模块顶层的语句出现
  • import 的模块名只能是字符串常量
  • import binding 是 immutable的

ES6模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是tree-shaking的基础。

所谓静态分析就是不执行代码,从字面量上对代码进行分析,ES6之前的模块化,比如我们可以动态require一个模块,只有执行后才知道引用的什么模块,这个就不能通过静态分析去做优化。

这是 ES6 modules 在设计时的一个重要考量,也是为什么没有直接采用 CommonJS,正是基于这个基础上,才使得 tree-shaking 成为可能,这也是为什么 rollup 和 webpack 2 都要用 ES6 module syntax 才能 tree-shaking。

详细说明:juejin.cn/post/684490…
首屏需要加载依赖包,像element-ui这样特别大的组件,以按需引入的方式替代全局引用来节省包的大小。
原理:element专门开发了babel插件babel-plugin-component,使得用户能以import { Button, Message } form 'antd'这样的方式去按需加载。本质上就是通过插件将上一句的代码又转化成如下:

import Button from 'antd/lib/button';
import Message from 'antd/lib/button';

这样似乎是最完美的变相tree-shaking方案。唯一不足的是,对于组件库开发者来说,需要专门开发一个babel插件;对于使用者来说,需要引入一个babel插件,稍微略增加了开发成本与使用成本。

image.png

全局引入方式:

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI)

按需引入:

  //引入表单
  import Vue from 'vue'
  import { Form, FormItem, Input} from 'element-ui'

  Vue.use(Form)
  Vue.use(FormItem)
  Vue.use(Input)

在 .babelrc文件中添加( vue-cli 3要先安装 babel-plugin-component)

babel-plugin-import 是 ant-design 团队出的,可以说其他绝大部分按需引入的插件都是在此基础上魔改的。 babel-plugin-component 是饿了么团队在前者的基础上做了一些改动。 原理就是babel通过核心babylon将ES6代码转换成AST抽象语法树,然后插件遍历语法树找出类似import {Button} from 'element-ui'这样的语句,进行转换,最后重新生成代码

npm install babel-plugin-component -D

然后

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

接下来,如果你只希望引入部分组件,比如 Button 和 Select,那么需要在 main.js 中写入以下内容:

import Vue from 'vue';
import { Button, Select } from 'element-ui';
import App from './App.vue';

Vue.component(Button.name, Button);
Vue.component(Select.name, Select);
/* 或写为
 * Vue.use(Button)
 * Vue.use(Select)
 */

new Vue({
  el: '#app',
  render: h => h(App)
});

CDN 与 gzip 减少项目打包体积

项目打包好部署到服务器上,首页加载时间需要7秒以上,这肯定是不友好的,看了看,是因为app.css、vendor.js文件居然达到了2M以上,造成了加载时间过长,开始考虑优化打包

chainWebpack: config => {
  if (process.env.NODE_ENV === 'production') {
      //因把成熟的第三方库才用CDN的方式,故忽略生成环境打包的文件
      let externals = {
          'vue': 'Vue',
          'axios': 'axios',
          'element-ui': 'ELEMENT',
          'vue-router': 'VueRouter',
          'vuex': 'Vuex',
          'echarts': 'echarts'
      }
      config.externals(externals)

      // 在html文件中引入相关CDN
      const cdn = {
          css: [
              // element-ui css
              'https://cdn.bootcss.com/element-ui/2.13.0/theme-chalk/index.css'
          ],
          js: [
              // vue
              'https://cdn.staticfile.org/vue/2.5.22/vue.min.js',
              // vue-router
              'https://cdn.staticfile.org/vue-router/3.0.2/vue-router.min.js',
              // vuex
              'https://cdn.staticfile.org/vuex/3.1.0/vuex.min.js',
              // axios
              'https://cdn.staticfile.org/axios/0.19.0-beta.1/axios.min.js',
              // element-ui js
              'https://cdn.bootcss.com/element-ui/2.13.0/index.js',
              //echarts
              'https://cdn.bootcss.com/echarts/4.6.0/echarts-en.common.js'
          ]
      }
      config.plugin('html')
          .tap(args => {
              args[0].cdn = cdn
              return args
          })
  }
}

main.js把之前引入的代码注释掉

// elemntui组件库,打包部署采用cdn需注释掉
// import ElementUI from 'element-ui';
// import 'element-ui/lib/theme-chalk/index.css';
// import 'element-ui/lib/theme-chalk/display.css';
// Vue.use(ElementUI);

用cdn的方式添加到index.html 文件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <% if (process.env.NODE_ENV === 'production') { %>

      <% for(var css of htmlWebpackPlugin.options.cdn.css) { %>
        <link href="<%=css%>" rel="preload" as="style">
        <link rel="stylesheet" href="<%=css%>" as="style">
      <% } %>
      <% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
        <link href="<%=js%>" rel="preload" as="script">
        <script src="<%=js%>"></script>
      <% } %>

    <% } %>
    <title>bxxt</title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but bxxt doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

最后进行打包

但是使用这种方法打包后的文件体积会减少,但是怎么说呢,减少了0.xM,首页效果还是不明显,而且后面在刷页面的时候会偶发找不到cdn资源,这样就不好了,所以考虑用第二种方法

vue.config.js文件中进行配置

  configureWebpack: {
     plugins: [
        new CompressionPlugin({
            algorithm: 'gzip', // 使用gzip压缩
            test: /\.js$|\.html$|\.css$/, // 匹配文件名
            filename: '[path].gz[query]', // 压缩后的文件名(保持原文件名,后缀加.gz)
            minRatio: 1, // 压缩率小于1才会压缩
            threshold: 10240, // 对超过10k的数据压缩
            deleteOriginalAssets: false, // 是否删除未压缩的源文件,谨慎设置,如果希望提供非gzip的资源,可不设置或者设置为false(比如删除打包后的gz后还可以加载到原始资源文件)
        }),
     ]
  }

这里前端配置工作结束,还需要后端配置一个nginx

理由:浏览器请求xx.js/css等文件时,服务器返回对应的xxx.js.gz文件,所以还需要在服务器配置一个属性,以期望它能正常返回我们需要的gz文件。

nginx.conf文件

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    gzip_static on;

    server {
        listen       8462;
        server_name  localhost;

        location / {
            root   dist;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

}

果然,文件小了5-6倍,文件体积减小还是非常明显的,首页1-2秒就加载出来了

有选择的使用prefetch和preload

prefetch

<link rel="prefetch" ></link>

这段代码告诉浏览器,这段资源将会在未来某个导航或者功能要用到,但是本资源的下载顺序权重比较低。也就是说prefetch通常用于加速下一次导航,而不是本次的。

preload

<link rel="preload" ></link>

preload通常用于本页面要用到的关键资源,包括关键js、字体、css文件。preload将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度。

在使用Vue Cli生成的项目里,当我们配置了路由懒加载后,默认情况下webpack在构建时会对所有的懒加载资源进行prefetchpreload,所以当你打开首页时,会看到大量的prefetchpreload请求,如下图:

虽说prefetch会在浏览器空闲时,下载相应文件,但这是一个很笼统的定义,这些大量的预加载资源会占用浏览器的资源,可能会导致一些关键的api请求或者图片请求受影响,所以对于这种情况,可以选择指定一些资源进行预加载或者禁止掉这些预加载,代码如下:

// 禁止prefetch和preload
chainWebpack: (config) => {
  config.plugins.delete('prefetch')
  config.plugins.delete('preload')
}
// 有选择的prefetch和preload
config.plugin('prefetch').tap(options => {
    options[0].fileBlacklist = options[0].fileBlacklist || []
    options[0].fileBlacklist.push(/myasyncRoute(.)+?.js$/)
    return options
})

上面代码修改vue.config.jschainWebpack来添加配置。

runtime.js 处理策略

根据路由驱动页面的 runtime 代码默认情况是包含在 build 后的 app.hash.js 内的,如果我们改动其他路由,就会导致 runtime 代码改变。从而不光我们改动的路由对应的页面 js 会变,含 runtime 代码的 app.hash.js 也会变,对用户体验是非常不友好的。

为了解决这个问题要设定 runtime 代码单独抽取打包:

config.optimization.runtimeChunk('single')

但是 runtime 代码由于只是驱动不同路由页面的关系,代码量比较少,请求 js 的时间都大于执行时间了,所以使用 script-ext-html-webpack-plugin 插件将其内链在 index.html 中比较友好。

script-ext-html-webpack-plugin

该插件需要在 html-webpack-plugin 插件初始化后运行,还需要安装 html-webpack-plugin:

image.png

接下来使用链式 webpack 语法配置 script-ext-html-webpack-plugin:

image.png 如此一来 runtime 代码就会内联在 index.html 中,效果:

image.png 下次变动哪个路由就只有被改动的路由 js 会改变,对其他路由 js 文件没有影响。

vite和webpack的区别

打包原理比较

打包过程原理
webpack识别入口->逐层识别依赖->分析/转换/编译/输出代码->打包后的代码逐级递归识别依赖,构建依赖图谱->转化AST语法树->处理代码->转换为浏览器可识别的代码
vite在打包比如组件库,或者一些较大的依赖(可能有上百个模块的库),这一部分使用 esbuild 来进行 预构建依赖 , esbuild 使用的是 Go 进行编写,比 JavaScript 编写的打包器预构建依赖快 10-100倍基于浏览器原生 ES module,利用浏览器解析 imports,服务器端按需编译返回
vite原理
  • 当声明一个 script 标签类型为 module 时

如:

 <script type="module" src="/src/main.js"></script>
  • 浏览器就会像服务器发起一个GET
http://localhost:3000/src/main.js请求main.js文件:
 
// /src/main.js:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
  • 浏览器请求到了main.js文件,检测到内部含有import引入的包,又会对其内部的 import 引用发起 HTTP 请求获取模块的内容文件
  • 如:GET http://localhost:3000/@modules/vue.js
  • 如:GET http://localhost:3000/src/App.vue
  • Vite 的主要功能就是通过劫持浏览器的这些请求,并在后端进行相应的处理将项目中使用的文件通过简单的分解与整合,然后再返回给浏览器,vite整个过程中没有对文件进行打包编译,所以其运行速度比原始的webpack开发编译速度快出许多!

刚刚咱们说的都是开发环境 ,也说了, Vite 在是直接把转化后的 es module 的JavaScript,扔给浏览器,让浏览器根据依赖关系,自己去加载依赖。

那有人就会说了,那放到 生产环境 时,是不是可以不打包,直接在开个 Vite 服务就行,反正浏览器会自己去根据依赖关系去自己加载依赖。答案是不行的,为啥呢:

1、你代码是放在服务器的,过多的浏览器加载依赖肯定会引起更多的网络请求

2、为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割、gzip压缩,CDN请求等这些优化操作,目前 esbuild 还不怎么完善

所以 Vite 最后的打包是使用了 Rollup

rollup

Rollup 编译资源离不开 plugin

rollup 也是一个 JavaScript 的模块化编译工具,可以帮助我们处理资源。

与webpack比较

rollup相比 webpack 理念更为简单,能处理的场景也更有限。

资源类型处理方式应用场景
webpack所有资源loader、plugin大型项目
rollupES Module 为主plugin库文件

命令行

通过 npm install rollup -D 先安装到项目中,也可以全局安装。在 src 文件夹下增加入口 index.js 文件,编写代码后使用命令行 npx rollup ./src/index.js -f iife -o dist/bundle.js编译。

-o 表示输出目录
-c 表示使用默认文件
-w 表示 --watch 监听文件
-f 表示 --format 格式化方式,有四种类型

  • cjs ( commonjs 的执行命令 )
  • iife ( esmodule 的执行命令,编译后文件会将代码放到闭包中 )
  • amd ( amd 方式的执行命令,编译后文件会通过 define 来定义 )
  • umd --name xxUtils ( 表示对多个环境支持,需要指定 name,作为全局的属性 )

编译后文件内容按原样输出

ps:有些时候,我们只是临时想要使用一些 cli 工具,比如 create-react-app,我们可能只是需要生成一个 React 项目。但是 npm 不能够在不将包安装到本地的情况下,使用相关的依赖,所以 npx 出现了。

使用NPX 使用NPX,您可以运行和执行软件包,而无需在本地或全局安装它们。 使用NPX运行NPM可执行文件时 如果安装了包,NPX将搜索包二进制文件(本地或全局),然后运行包。

如果之前未安装该软件包,NPX将不会在您的系统中安装该软件包;相反,它将创建一个临时缓存来保存包二进制文件。一旦执行结束,NPX将从系统中删除已安装的缓存二进制文件。

配置文件

命令行可以直接使用,当处理规则较多时命令行需要定义很长,还是通过配置文件会更为方便,默认的配置文件为 rollup.config.js

配置文件中使用 input 定义入口文件,output 定义编译后文件位置,可定义多个,因为 rollup 主要支持 esmodule,所以使用 export default 的方式导出。

export default {
  input: './src/index.js',
  output: [
    {
      format: 'umd',
      name: 'iceUtils',
      file: './dist/ice.umd.js',
    },
    {
      format: 'iife',
      name: 'iceUtils',
      file: './dist/ice.iife.js',
    },
    {
      format: 'cjs',
      file: './dist/ice.cjs.js',
    },
    {
      format: 'amd',
      file: './dist/ice.amd.js',
    },
  ],
};

通过 npx rollup -c 即可通过配置文件编译出四份代码。

支持 commonjs

rollup 中默认不支持 commonjs ,如果使用 module.exports 这种方式导出。

// math.js
const sum = (a, b) => a + b;
const mul = (a, b) => a * b;
module.exports = {
  sum,
  mul,
};

// index.js
const { sum, mul } = require('./math');
console.log(sum(10, 20));
console.log(mul(10, 20));

代码可以通过编译,但将 js 文件引入到 html 文件中,浏览器将无法识别

使用 @rollup/plugin-commonjs 可以解决这个问题

import commonjs from '@rollup/plugin-commonjs';
export default {
  input: './src/index.js',
  output: [
    {
      format: 'cjs',
      file: './dist/ice.cjs.js',
      exports: 'auto',
    },
  ],
  plugins: [commonjs()],
};

// index.js 要通过 esmodule 的方式引入
import { sum, mul } from './math';

如果引入了第三方资源,如 lodash,要使用 @rollup/plugin-node-resolve 来对资源进行解析,此时第三方资源会被打包进编译后的文件,这样使得编译后文件的体积非常大,通过 external 属性排除打包,并在 html 页面引入资源地址。

import commonjs from '@rollup/plugin-commonjs';
import nodeResolve from '@rollup/plugin-node-resolve';

export default {
  input: './src/index.js',
  output: [
    {
      format: 'umd',
      file: './dist/ice.umd.js',
      name: 'iceUtils',
      globals: { lodash: '_' },
    },
  ],
  external: ['lodash'],
  plugins: [commonjs(), nodeResolve()],
};

// index.html
<script src="./node_modules/lodash/lodash.min.js"></script>
<script src="./dist/ice.umd.js"></script>

处理css和vue

将 css 资源引入直接编译会报错,告知我们需要合适的 plugin

css 使用 rollup-plugin-postcss 来处理,如果项目中有 vue 文件,则需要通过 rollup-plugin-vue 来处理,rollup-plugin-replace 定义全局变量。

import vue from 'rollup-plugin-vue';
import replace from 'rollup-plugin-replace';

plugins: [
  vue(),
  replace({
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
  }),
],

转换和压缩

以上代码转换后都与原编写文件一致,没有进行转换和压缩,在 webpack 中使用到的是 babel 和 terser 工具,在 rollup 中也类似。

import babel from '@rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser';

plugins: [
  babel({
    babelHelpers: 'bundled',
  }),
  terser(),
],

这样编译后的资源就经过了代码转换和压缩

本地服务

本地服务通过 rollup-plugin-serve 开启,当资源文件发生变化时,rollup-plugin-livereload 会实时刷新浏览器。

import serve from 'rollup-plugin-serve';
import livereload from 'rollup-plugin-livereload';

plugins: [
  serve({
    open: true,
    port: 8000,
    contentBase: '',
  }),
  livereload(),
],

环境区分

上面的 plugin 都是写到一块的,没有区分开发模式或者生产模式,每次编译都会用到所有的插件,我们可以通过参数来做一个区分。

// 在 package.json 中定义指令
"scripts": {
    "build": "npx rollup -c --environment NODE_ENV:production",
    "serve": "rollup -c --environment NODE_ENV:development -w"
},

// rollup.config.js
const isProduction = process.env.NODE_ENV === 'production';
const plugins = [
  commonjs(),
  nodeResolve(),
  replace({
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
  }),
  postcss(),
  vue(),
];

if (isProduction) {
  const prodPlugin = [
    babel({
      babelHelpers: 'bundled',
    }),
    terser(),
  ];
  plugins.push(...prodPlugin);
} else {
  const devPlugin = [
    serve({
      open: true,
      port: 8000,
      contentBase: '',
    }),
    livereload(),
  ];
  plugins.push(...devPlugin);
}

这样编译开发环境就可以直接通过指令 npm run build,编译生产模式则用 npm run serve 来执行

总结

rollup 主要用于处理 esmodule 的 js 资源,通过命令行可以直接执行,需要指定入口出口文件,以及编译的方式。

默认不被支持的资源处理需要通过 plugin,自己通过 commonjs 导出的资源使用 @rollup/plugin-commonjs,第三方库解析通过 @rollup/plugin-node-resolve,处理 css 需要 rollup-plugin-postcss,vue 得依赖 rollup-plugin-vue 和 rollup-plugin-replace,转换和压缩离不开 @rollup/plugin-babel 和 rollup-plugin-terser,最后通过 rollup-plugin-serve 和 rollup-plugin-livereload 开启服务。

区分环境通过 --environment 配置参数。

后记:

vue cli的其他详细配置说明: blog.csdn.net/qq_21567385… blog.csdn.net/qq_21567385…