如何使用Rollup来打包样式表并实现自动重载页面预览

5,205 阅读9分钟
原文链接: www.zcfy.cc

不想使用Grunt和Gulp?这篇教给你如何使用js打包工具Rollup,我先手把手告诉你怎么使用PostCSS来处理样式表吧!

在这个系列中你将学到:

  • 如何使用Rollup来更加灵活而高效地打包JavaScript。
  • 如何使用Rollip来打包样式表并实现自动重载页面预览。

本文内容

在本系列的第一部分中,我们已经学习了如何开始使用Rollup来打包前端的js代码,这篇文章我们将学习本系列的另外两部分。

我们将继续使用之前的项目文件。

首先,在Part II 中,我们将使用Rollup来对css进行打包。通过使用PostCSS,我们就可以对css进行一些变换处理,这样我们就能在css代码中使用一些语法糖,比如说更简单的变量语法以及嵌套规则。

在Part III中,我们将增加文件监听和自动重载页面,这样当文件发生变化的时候,就无需手动重新生成打包文件。

准备工作

我们将继续使用我们之前的那个项目,如果你还没有看过第一部分的文章,你可以先看一下,很值得一看哦!

注意:如果你还没有下载过这个项目,你可以直接使用这个命令从github上下载第一部分结束时候的文件,就不用从头开始了:

git clone -b part-2-starter --single-branch [https://github.com/jlengstorf/learn-rollup.git](https://github.com/jlengstorf/learn-rollup.git)

系列导航

第二部分:如何在你的下一个项目中使用Rollup.js:关于PostCSS

Rollup的这个部分也很有趣,取决与你如何搭建你的项目。你可以很轻松的使用Rollup来处理CSS文件,然后把它插入到HTML的标签中。

好的一面在于, 我们可以将构建项目的步骤集中在一处,从而降低开发过程中的复杂程度——这很有用,尤其是当我们进行团队合作的时候。

这样做也有不好的一面,我们使我们的css依赖JavaScript文件,并且在插入样式之前创建了一个未加载样式的HTML文件。这样做可能对于一些项目来说是没有意义的,我们应该做出权衡。

既然这篇文章是关于Rollup的,那。。。。啥也不管了,开干吧!

Step 0 :在main.js中加载样式表

如果你从未使用过构建工具的话,下面的操作可能有点过于“时髦”了,不过跟紧我,别担心。 通常来说,如果我们要给HTML文件加上样式的话,我们会使用一个<link>标签,但今天不这么做,我们在main.min.js文件中使用import语句。

src/scripts/main.js的最开始,加载样式表:

+ // Import styles (automatically injected into <head>).
+ import '../styles/main.css';

  // Import a couple modules for testing.
  import { sayHelloTo } from './modules/mod1';
  import addArray from './modules/mod2';

  // Import a logger for easier debugging.
  import debug from 'debug';
  const log = debug('app:log');

  // The logger should only be disabled if we’re not in production.
  if (ENV !== 'production') {

    // Enable the logger.
    debug.enable('*');
    log('Logging is enabled!');
  } else {
    debug.disable();
  }

  // Run some functions from our imported modules.
  const result1 = sayHelloTo('Jason');
  const result2 = addArray([1, 2, 3, 4]);

  // Print the results on the page.
  const printTarget = document.getElementsByClassName('debug__output')[0];

  printTarget.innerText = `sayHelloTo('Jason') => ${result1}\n\n`;
  printTarget.innerText += `addArray([1, 2, 3, 4]) => ${result2}`;

Step 1:下载PostCSS作为Rollup的插件

首先,需要下载一个Rollup的PostCSS插件,使用下面的方式进行下载:

`npm install --save-dev rollup-plugin-postcss`

Step2: 更新rollup.config.js

接下来,在rollup.config.js加上这个插件

 // Rollup plugins
  import babel from 'rollup-plugin-babel';
  import eslint from 'rollup-plugin-eslint';
  import resolve from 'rollup-plugin-node-resolve';
  import commonjs from 'rollup-plugin-commonjs';
  import replace from 'rollup-plugin-replace';
  import uglify from 'rollup-plugin-uglify';
+ import postcss from 'rollup-plugin-postcss';

  export default {
    entry: 'src/scripts/main.js',
    dest: 'build/js/main.min.js',
    format: 'iife',
    sourceMap: 'inline',
    plugins: [
+     postcss({
+       extensions: [ '.css' ],
+     }),
      resolve({
        jsnext: true,
        main: true,
        browser: true,
      }),
      commonjs(),
      eslint({
        exclude: [
          'src/styles/**',
        ]
      }),
      babel({
        exclude: 'node_modules/**',
      }),
      replace({
        ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
      }),
      (process.env.NODE_ENV === 'production' && uglify()),
    ],
  };

好了,看下生成的打包文件吧!

下载就可以处理样式表了,我们可以重新生成一下打包文件,看下它的工作原理。

执行./node_modules/.bin/rollup -c,接着看下在build/js/main.min.js生成的打包文件,在文件的最顶端。你可以看到一个新的函数,名为__$styleInject()

function __$styleInject(css) {
  css = css || '';
  var head = document.head || document.getElementsByTagName('head')[0];
  var style = document.createElement('style');
  style.type = 'text/css';
  if (style.styleSheet){
    style.styleSheet.cssText = css;
  } else {
    style.appendChild(document.createTextNode(css));
  }
  head.appendChild(style);
}
__$styleInject("/* Styles omitted for brevity... */");

这个函数的工作是创建一个<style>标签,并把这个样式表作为它的内容,插入到文件的<head>标签中

紧挨着这个函数声明语句,我们可以看到他被调用了,输出了由PostCSS处理过的样式。相当的酷😎,是吧!

现在为止,这些样式还没有真正的被处理过,因为PostCSS只是把样式表原封不动的传了过来。为了让样式可以在浏览器上顺利展现,需要添加一些PostCSS的插件!

Step 3:下载需要的PostCSS插件

我爱PostCSS!起初,我是站在LESS的一边的。但当所有人都抛弃了LESS而转投Sass阵营的时候,我或多或少有一种被强迫的感觉,直到我发现了PostCSS的存在,真是太TM开心了!

我喜欢它的原因是,它可以让我使用LESS和Sass中我喜欢的部分——嵌套,简化的变量——而不必去触碰Less和Sass中那些美丽而危险的毒苹果,比如说逻辑操作。

而众多优点中我最喜欢的就是PostCSS是插件化的,而不是写成了一个包罗万象的结构化语言。我们可以只使用真正需要的那些特性,而甩开那些不想用的特性,这点很重要!

因此在项目中,我只使用了4个插件——其中两个是语法糖插件,一个是在旧的浏览器中对新的CSS特性的支持插件,另外一个是压缩最终的样式表的插件

  • postcss-simple-vars—— 这个插件允许你试用 Sass式的变量,比如说:可以声明$myColor:#fff,并在代码中像color:$myColor这样使用,而在常规的写法中,需要写成root { --myColor:#fff;}才能在代码中color:var(--myColor);来使用它。当然这个是个人喜好问题,只是我比较喜欢更为简洁的语法。
  • postcss-nested——这个插件允许你使用嵌套的语法。但是我一般不使用嵌套规则,我只是用嵌套语法来更加简便地创建一个BEM友好的选择器,将块、元素、修饰器能够写进一个CSS语法块中。
  • postcss-cssnext—— 这个插件可以让你使用更为现代甚至是面向未来的CSS语法(规则参考latest CSS specs)并将代码转换为可以在不支持这些语法的旧浏览器上使用。
  • cssnano——这个插件可以将输出的CSS压缩和简化。你可以将这个类比为JavaScript的UglifyJS

使用下面的命令来下载这些插件

`npm install --save-dev postcss-simple-vars postcss-nested postcss-cssnext cssnano`

Step4: 更新rollup.config.js

接下来,在postcss的配置对象中增加一个plugins属性,将PostCSS插件引入到rollup.config.js中。

 // Rollup plugins
  import babel from 'rollup-plugin-babel';
  import eslint from 'rollup-plugin-eslint';
  import resolve from 'rollup-plugin-node-resolve';
  import commonjs from 'rollup-plugin-commonjs';
  import replace from 'rollup-plugin-replace';
  import uglify from 'rollup-plugin-uglify';
  import postcss from 'rollup-plugin-postcss';

+ // PostCSS plugins
+ import simplevars from 'postcss-simple-vars';
+ import nested from 'postcss-nested';
+ import cssnext from 'postcss-cssnext';
+ import cssnano from 'cssnano';

  export default {
    entry: 'src/scripts/main.js',
    dest: 'build/js/main.min.js',
    format: 'iife',
    sourceMap: 'inline',
    plugins: [
      postcss({
+       plugins: [
+         simplevars(),
+         nested(),
+         cssnext({ warnForDuplicates: false, }),
+         cssnano(),
+       ],
        extensions: [ '.css' ],
      }),
      resolve({
        jsnext: true,
        main: true,
        browser: true,
      }),
      commonjs(),
      eslint({
        exclude: [
          'src/styles/**',
        ]
      }),
      babel({
        exclude: 'node_modules/**',
      }),
      replace({
        ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
      }),
      (process.env.NODE_ENV === 'production' && uglify()),
    ],
  };

注意:我们需要为cssnext()传入{ warnForDuplicates:false },因为cssnext()cssnano()都会使用到Autoprefixer,这会触发一个警告。我们知道Autoprefixer会执行两次(在这个例子中不会带来什么不好的后果)并且不会报出警告,相比较放弃使用这两个插件,这样似乎会更好一些。

在中查看下输出

安装了插件之后,运行下./node_modules/.bin/rollup -c然后在浏览器中打开build/index.html。现在页面有样式了,如果打开控制台的话,就能看到样式表被插入到文件的头部,我们期待PostCSS做的东西它都完成得很棒: 加上浏览器前缀、压缩和简化了css代码等。

整个样式表被PostCSS处理过后通过Rollup插入到文件中

太棒了!现在就有了一个相当棒的打包流程:我们打包了JavaScript文件,移除了无用代码,输出经过了压缩和简化处理,样式表也被PostCSS处理后插入到了文件的头部。

然而,还有个烦心事儿,在修改代码后,我们需要手动重新打包,扎心啊!

所以,在下面的这个部分中,我们将使用Rollup来对文件的修改进行监听,并且实现当文件发生改变后浏览器就会自动重新加载。

Part III: 在你下一个项目中使用Rollup:自动重载

到现在为止,虽然文件可以成功地进行JavaScript和样式表的打包,但是这一些的处理都是手动的。由于在过程中的每一个手动操作相比于自动化的步骤都有更高的失败风险——并且每当修改一个文件的时候都得重新运行一下./node_modules/.bin/rollup -c这实在过于蛋疼——我们想要让整个打包的流程自动化起来、

注意:如果你还没有下载过这个项目,你可以直接使用这个命令从git上下载第二部分结束时候的文件,就不用从头开始了: git clone -b part-3-starter --single-branch [https://github.com/jlengstorf/learn-rollup.git](https://github.com/jlengstorf/learn-rollup.git)

Step0 : 为Rollup增加一个监听插件

当使用Node.js的时候,监听器(watcher)是一个很常用的工具。如果你使用过webpack、Grunt、Gulp或者其他构建工具的话,你应该感到很熟悉。

监听器是一个运行程序,当你进行一个项目的时候,他会监听你的文件夹,如果你修改了其中的文件,他就会触发一个动作。在构建工具中,最常见的动作就是触发重建操作。

在项目中,我们想要监听src/这个文件夹中的文件修改,一旦监听器发现文件有改动,就会告诉Rollup去重新生成打包文件。

为了实现这个目标,我们将使用rollup-watch这个插件,这个插件和迄今为止使用的插件有点不同——但仅仅是一点点的不同。现在来安装这个插件:

`npm install --save-dev rollup-watch`

Step1:加上--watch标志,运行Rollup

rollup-watch和其他Rollup的插件不同之处在于,我们不需要修改rollup.config.js就可以使用它。

而是,需要在终端的命令行中加上一个标志:

# Run Rollup with the watch plugin enabled
./node_modules/.bin/rollup -c --watch

在运行这个命令之后,可以看到控制台输出的代码有点不一般:

$ ./node_modules/.bin/rollup -c --watch
checking rollup-watch version...
bundling...
bundled in 949ms. Watching for changes...

这个进程依旧是在运行的,现在它就在监听我们的所有的变化。

所以,对src/main.js进行个小小的修改——比如说添加一个注释的话——马上就可以看到一个新的打包文件生成了。

由于监听器在运行,文件的修改就触发了一个重建的操作lint也马上就捕获了错误。很优雅,是吧!

监听器可以在开发中节省大量的时间,但也是可以进一步优化整个开发流程的。当更新了打包文件后,我们还需要去刷新浏览器——所以, 我们现在增加一个新的的工具,当打包文件更新的时候,它可以自动刷新浏览器。

小提示:想要停止监听进程,可以在终端窗口中按下control + C

Step2: 下载LiveReload,自动刷新浏览器

加快开发速度的常用工具之一是LiveReload。这是一个跑在后台的进程,他会监听文件的改变,并告诉浏览器刷新。

首先,需要下载插件:

`npm install --save-dev livereload`

Step3:将LIveReload脚本插入项目中

在LiveReload工作之前,需要在页面中插入一个脚本和LiveReload 服务器进行通信。

由于只需要在开发过程中使用到,我们可以利用环境变量,只在非生产状态时插入这段脚本。

src/main.js添加如下代码:

 // Import styles (automatically injected into <head>).
  import '../styles/main.css';

  // Import a couple modules for testing.
  import { sayHelloTo } from './modules/mod1';
  import addArray from './modules/mod2';

  // Import a logger for easier debugging.
  import debug from 'debug';
  const log = debug('app:log');

  // The logger should only be disabled if we’re not in production.
  if (ENV !== 'production') {

    // Enable the logger.
    debug.enable('*');
    log('Logging is enabled!');

+   // Enable LiveReload
+   document.write(
+     '<script src="http://' + (location.host || 'localhost').split(':')[0] +
+     ':35729/livereload.js?snipver=1"></' + 'script>'
+   );
  } else {
    debug.disable();
  }

  // Run some functions from our imported modules.
  const result1 = sayHelloTo('Jason');
  const result2 = addArray([1, 2, 3, 4]);

  // Print the results on the page.
  const printTarget = document.getElementsByClassName('debug__output')[0];

  printTarget.innerText = `sayHelloTo('Jason') => ${result1}\n\n`;
  printTarget.innerText += `addArray([1, 2, 3, 4]) => ${result2}`;

修改完,保存文件,下面是见证奇迹的时刻:

注意:虽说是否了解LiveReload服务器的工作原理并不重要,但我也给你简单的介绍一下。跑在命令行的进程会监听文件的改变,一旦文件变化,它就会通过websock向客户端脚本发送一个消息,从而触发刷新行为。

Step4:运行LiveReload

在下载LiveReload并且也将脚本插入到文件中之后,就可以启动它监听build/目录了。

`./node_modules/.bin/livereload 'build/'`

注意:监听build/的原因是,在重新打包后都会在这个目录中生成一个打包文件

输出的结果与下面的比较类似:

$ ./node_modules/.bin/livereload 'build/'
Starting LiveReload v0.5.0 for /Users/jlengstorf/dev/code.lengstorf.com/projects/learn-rollup/build on port 35729.

如果打开浏览器来看build/index.html这个页面的话——确定在启动了LiveReload并且保证socket连接被激活的后刷新页面——就能看到在build/index.html中进行的改变被自动重载并展现在我们面前:

文件的改变触发了浏览器的重载

太棒了,但是还有一点不完美:现在,要么运行Rollup的监听,要么运行LiveReload,除非在终端中开启了多个窗口,否则不能让这两个操作一起运行。这很不爽!在接下来的步骤中,就来解决这个问题。

Step 5: 使用package.json脚本来简化启动流程

在至今为止的教程中,我们需要执行rollup的整个路径才能运行打包程序——相信这一点你也注意到了——好麻烦啊!

因为这点,也因为接下来要使用的工具,我们需要在package.json写一个脚本同时执行rolluplivereload的命令。

打开package.json,这个文件在learn-rollup/的最下面。在这个文件中,你会看到下面的代码:

{
  "name": "learn-rollup",
  "version": "0.0.0",
  "description": "This is an example project to accompany a tutorial on using Rollup.",
  "main": "build/js/main.min.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+ssh://git@github.com/jlengstorf/learn-rollup.git"
  },
  "author": "Jason Lengstorf <jason@lengstorf.com> (@jlengstorf)",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/jlengstorf/learn-rollup/issues"
  },
  "homepage": "https://github.com/jlengstorf/learn-rollup#readme",
  "devDependencies": {
    "babel-preset-es2015-rollup": "^1.2.0",
    "cssnano": "^3.7.4",
    "livereload": "^0.5.0",
    "npm-run-all": "^3.0.0",
    "postcss-cssnext": "^2.7.0",
    "postcss-nested": "^1.0.0",
    "postcss-simple-vars": "^3.0.0",
    "rollup": "^0.34.9",
    "rollup-plugin-babel": "^2.6.1",
    "rollup-plugin-commonjs": "^3.3.1",
    "rollup-plugin-eslint": "^2.0.2",
    "rollup-plugin-node-resolve": "^2.0.0",
    "rollup-plugin-postcss": "^0.1.1",
    "rollup-plugin-replace": "^1.1.1",
    "rollup-plugin-uglify": "^1.0.1",
    "rollup-watch": "^2.5.0"
  },
  "dependencies": {
    "debug": "^2.2.0"
  }
}

看到了scripts这个属性了吗?在里面再添加两条:

  1. 第一个脚本是运行Rollup生成打包文件的命令(之前一直是手动执行的./node_modules/.bin/rollup -c --watch
  2. 第二个脚本是启动LiveReload(之前使用./node_modules/.bin/livereload 'build/'这个命令执行)

package.json添加如下代码:

 {
    "name": "learn-rollup",
    "version": "0.0.0",
    "description": "This is an example project to accompany a tutorial on using Rollup.",
    "main": "build/js/main.min.js",
    "scripts": {
+     "dev": "rollup -c --watch",
+     "reload": "livereload 'build/'",
      "test": "echo \"Error: no test specified\" && exit 1"
    },
    "repository": {
      "type": "git",
      "url": "git+ssh://git@github.com/jlengstorf/learn-rollup.git"
    },
    "author": "Jason Lengstorf <jason@lengstorf.com> (@jlengstorf)",
    "license": "ISC",
    "bugs": {
      "url": "https://github.com/jlengstorf/learn-rollup/issues"
    },
    "homepage": "https://github.com/jlengstorf/learn-rollup#readme",
    "devDependencies": {
      "babel-preset-es2015-rollup": "^1.2.0",
      "cssnano": "^3.7.4",
      "livereload": "^0.5.0",
      "npm-run-all": "^3.0.0",
      "postcss-cssnext": "^2.7.0",
      "postcss-nested": "^1.0.0",
      "postcss-simple-vars": "^3.0.0",
      "rollup": "^0.34.9",
      "rollup-plugin-babel": "^2.6.1",
      "rollup-plugin-commonjs": "^3.3.1",
      "rollup-plugin-eslint": "^2.0.2",
      "rollup-plugin-node-resolve": "^2.0.0",
      "rollup-plugin-postcss": "^0.1.1",
      "rollup-plugin-replace": "^1.1.1",
      "rollup-plugin-uglify": "^1.0.1",
      "rollup-watch": "^2.5.0"
    },
    "dependencies": {
      "debug": "^2.2.0"
    }
  }

这些脚本可以用一种很简单的方式执行我们的操作。

现在可以使用npm run dev来运行Rollup,使用npm run reload来运行LiveReload。

最后需要做的是让他们一起跑起来。

Step6:安装一个可以让监听器和LiveReload并行的工具

为了让Rollup和LiveReload可以同时运行,需要安装一个名为npm-run-all的工具。

这个工具很强大,就不扩展地讲它的其他功能了。现在只需要让他将脚本并行执行——也就是在一个终端session中同时运行。

首先,安装npm-run-all

`npm install --save-dev npm-run-all`

接着,需要在package.json增加一个脚本调用npm-run-all。scripts属性中,增加以下的代码(为了简洁,其余部分我就不贴出来了):

 "scripts": {
      "dev": "rollup -c --watch",
      "reload": "livereload 'build/' -d",
+     "watch": "npm-run-all --parallel reload dev",
      "test": "echo \"Error: no test specified\" && exit 1"
    }

保存修改后,回到控制台。现在,准备好做最后一步!

Step7: 执行watch脚本

诶呀,就要成功了!

在终端里,运行如下命令:

`npm run watch`

接着刷新浏览器,修改CSS或者JS文件,保存后你就可以看到浏览器自己刷新并更新了打包文件——一切就像做梦一样。

LiveReload + 自动重新打包 让生活变得更美好。

我们已经学会使用Rollup了。代码打包文件变得更加小巧而快速,开发流程也因此更为简便而高效!

扩展阅读