经过这个教程学习如何使用JavaScript打包工具Rollup配合PostCSS来取代Grunt或Gulp处理样式文件。css
上一篇文章中,咱们完成了使用Rollup打包前端JavaScript入门。html
这篇文章包含Part II
和Part III
。前端
Part II会继续在上次的项目中进行,为Rollup添加处理样式的能力,使用PostCSS进行一些转换,让咱们能使用更简单的变量写法和嵌套规则等语法糖。node
而后完成Part III,圆满结束。第三部分将为项目添加文件监听和LiveReload,这样每当文件变化时就不用再手动地打包bundle文件了。react
咱们会在上周的项目基础上继续进行,所以若是你尚未看上一节,推荐你先看一下。webpack
NOTE: 若是你没有项目的副本,你能够经过这条命令克隆在Part I结束这个状态下的项目:
git clone -b part-2-starter --single-branch https://github.com/jlengstorf/learn-rollup.git
git
你能够轻松地处理CSS并注入到文档的head
中,这取决于你的项目如何配置,也是Rollup另外一个优势。github
另外,全部的构建过程都在一个地方,下降了开发流程的复杂度 - 这对咱们颇有帮助,尤为是在团队协做时。web
不过糟糕的是,咱们使得样式依赖JavaScript,而且在无样式HTML在样式注入前会产生一个短暂的闪烁。这对有些项目来讲是没法接受的,而且应该经过像使用PostCSS提取等方式解决。npm
既然这篇文章是关于Rollup的,那么:来吧。让咱们使用Rollup!
main.js
中加载样式。若是你以前历来没用过构建工具,可能感受这样有点怪,但请跟着我继续。为了在文档中使用样式,咱们不会像日常那样使用<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}`;
首先须要Rollup PostCSS插件,使用以下命令安装:
npm install --save-dev rollup-plugin-postcss
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()), ], };
如今咱们已经可以处理样式了,能够看一下新生成的bundle,看看它是如何工做的。
运行./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插件,使得样式能在目标浏览器上工做。
我爱PostCSS。我已经放弃LESS阵营了,当全部人都抛弃LESS时,我发现本身或多或少被影响加入了Sass阵营,后来PostCSS出现了我就很是开心地去学。
我喜欢它是由于它提供了部分在LESS和Sass中我喜欢的功能 - 嵌套,简单的变量 - 并且没有彻底放弃LESS和Sass中我认为诱人也危险的功能,好比逻辑运算。
我喜欢它的插件模式,而不是一个叫作“PostCSS”的语言。咱们能够只选择真正须要的特性 - 更重要的是,咱们能够移除不想要的特性。
所以在咱们的项目里,只会使用到四个插件 - 两个是语法糖,一个用来在兼容旧浏览器的新CSS特性,一个用来压缩,减小生成的样式文件大小。
postcss-simple-vars
— 可使用Sass风格的变量(e.g. $myColor: #fff;
,color: $myColor;
)而不是冗长的CSS语法(e.g. :root {--myColor: #fff}
,color: var(--myColor)
)。这样更简洁;我更喜欢较短的语法。
postcss-nested
— 容许使用嵌套规则。实际上我不用它写嵌套规则;我用它简化BEM友好的选择器的写法而且划分个人区块,元素和修饰到单个CSS块。
postcss-cssnext
— 这个插件集使得大多数现代CSS语法(经过最新的CSS标准)可用,编译后甚至能够在不支持新特性的旧浏览器中工做。
cssnano
— 压缩,减少输出CSS文件大小。至关于JavaScript中对应的UglifyJS。
使用这个命令安装插件:
npm install --save-dev postcss-simple-vars postcss-nested postcss-cssnext cssnano
rollup.config.js
。接下来,在rollup.config.js
引入PostCSS插件,在配置对象的plugins
属性上添加一个postcss
。
// 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()), ], };
NOTE: 在
cssnext()
中配置了{ warnForDuplicates: false }
是由于它和cssnano()
都使用了Autoprefixer,会致使一个警告。不用计较配置, 咱们只须要知道它被执行了两次(在这个例子中没什么坏处)而且取消了警告。
<head>
中的输出内容。插件安装完后,从新构建bundle文件。(./node_modules/.bin/rollup -c
),而后在浏览器中打开build/index.html
。能够看到页面是有样式的,若是咱们审查元素能够发现样式被注入到页面头部,压缩简化,添加全部浏览器前缀和其它咱们预期的优势:
样式被PostCSS处理并经过Rpllup注入
太棒了!咱们如今拥有十分可靠的构建流程:JavaScript会被打包,无用的代码会被移除,输出文件是通过压缩精简的,样式时经过PostCSS处理后注入到文档头部的。
然而,最后仍然存在一个痛点,每当咱们作了一些修改后都不得不手动地从新构建。所以在下个部分,咱们让Rollup监听文件的变化,每当有文件改变时就让浏览器自动从新载入文件。
如今,咱们的项目已经能够打包JavaScript和样式文件了,但仍然是一个手动的过程。并且因为过程的每一个步骤都是手动的,相比自动化流程失败风险更高 - 由于每次修改文件后都执行./node_modules/.bin/rollup -c
太痛苦了 - 咱们但愿自动从新构建bundle。
NOTE: 若是你没有项目的副本,你能够经过这条命令克隆在Part II结束这个状态下的项目::
git clone -b part-3-starter --single-branch https://github.com/jlengstorf/learn-rollup.git
基于Node.js的监听器是很常见的开发工具。若是你以前使用过webpack,Grunt,Gulp或者其余构建工具会很熟悉。
监听器是在一个项目中运行的进程,当你修改了它监听的文件夹内的任意文件时就会触发一个动做。对于构建工具而言,一般这个动做是触发从新构建。
在咱们的项目中,咱们须要监听src
目录下的任何文件,而且探测到文件变化后但愿Rollup从新打包。
为了达到目的,咱们使用rollup-watch
插件,它和前面的其它Rollup插件有些不一样 - but more on that in a bit。让咱们先安装插件:
npm install --save-dev rollup-watch
--watch
标识运行Rollup。rollup-watch
与其余插件的不一样就是使用这个插件不须要对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
作点小变化 - 好比加一条注释 - 在咱们保存修改的那一刻新的bundle文件就生成了。
监听程序执行时,变化会触发从新构建。LINTER会马上捕获错误。很优雅,不是吗?
这为咱们在开发过程当中节省了大量时间,不过还能够更进一步。如今咱们仍然须要手动刷新浏览器来获取更新后的bundle - 添加一个工具,在bundle更新后自动刷新浏览器。
TIP: 在终端窗口输入
control + C
结束监听进程。
LiveReload是加速开发的经常使用工具。它是一个跑在后台的进程,每当有它监听的文件变化时,他就会通知浏览器刷新。
先下载插件:
npm install --save-dev livereload
In src/main.js
, add the following:
在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}`;
作一些修改而后保存,如今咱们试试看。
NOTE: Livereload是如何工做的并不重要,简单的解释就是命令行进程监听文件变化,而后经过websockets向客户端脚本发送消息触发重加载。
LiveReload安装好而且脚本注入到文档中后,咱们能够运行它去监听build
目录:
./node_modules/.bin/livereload 'build/'
NOTE: 监听
build/
是由于咱们只须要在新的bundle产生时才从新构建。
输出结果像下面这样:
$ ./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
脚本的全路径,我猜你已经感受到这很蠢了。
所以咱们须要一个能同时运行监放任务和Livereload
的工具,先将这两个rollup
命令和LiveReload命令当作脚本写在package.json
中。
打开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": { "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
属性了吗?咱们将为它增长两个新属性:
一个运行Rollup打包的脚本(原先手动执行的./node_modules/.bin/rollup -c --watch
)
一个启动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。
接下来要作的就是让他们一块儿运行。
为了能同时执行Rollup和LiveReload,咱们要使用一个叫作npm-run-all
的工具。
这是一个强大的工具,咱们先不讨论他的所有功能。咱们如今只使用它并行执行脚本的能力 - 意味着同一个终端会话能够同时执行两个任务。
先安装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" }
保存修改,切换到终端。准备好最后一步!
watch
脚本。就像以前作的同样。
在终端中运行下面的命令:
npm run watch
而后刷新浏览器, 改变一下JS或CSS, 浏览器会加载更新后的bundle并自动刷新 - 太奇妙了!
Liveload + 自动构建如同魔法通常。
如今咱们是Rollup专家了。咱们的打包代码变得更小更快,开发流程也更轻松快速。
Some discussion about using JS to insert styles, and when/whether it’s appropriate
这篇文章的代码放在GitHub上。你能够fork 这个仓库进行修改或测试,开issue或者报告bug,或者新建pull request进行建议或者修改。