如何使用Rollupvue打包样式式文件并添加LiveReload

在 SegmentFault,解决技术问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。
一线的工程师、著名开源项目的作者们,都在这里:
获取验证码
已有账号?
标签:至少1个,最多5个
教程:如何使用Rollup打包JavaScript
通过这个系列教程一步一步学习如何使用更小更快的Rollup取代webpack和Browserify打包JavaScript文件。
这周,我们要使用Rollup构建我们的第一个项目,Rollup是一个打包JavaScript(和样式,不过下周才会做)的构建工具。
通过这个教程,我们的Rollup将能够:
合并scripts代码,
删除多余代码,
编译成对旧浏览器友好的代码,
支持在浏览器中使用Node模块,
能使用环境变量,
尽可能的压缩,减少文件大小。
至少懂一点JavaScript的话将会更好理解。
对有基本了解,不过不了解也无妨。
在你的设备上要有npm。(还没有?)
Rollup是什么?
用他们自己的话说:
Rollup是下一代JavaScript模块打包工具。开发者可以在你的应用或库中使用ES2015模块,然后高效地将它们打包成一个单一文件用于浏览器和Node.js使用。
和Browserify和webpack很像。
你也可以称Rollup是一个构建工具,可以和像Grunt和Gulp等一起配置使用。但是,需要注意的一点是当你使用Grunt和Gulp来处理像打包JavaScript这样的任务时,这些工具的底层还是使用了像Rollup,Browserify或webpack这些东西。
为什么应该关注Rollup?
Rollup最令人激动的地方,就是能让打包文件体积很小。这么说很难理解,更详细的解释:相比其他JavaScript打包工具,Rollup总能打出更小,更快的包。
因为Rollup基于ES2015模块,比webpack和Browserify使用的CommonJS模块机制更高效。这也让Rollup从模块中删除无用的代码,即tree-shaking变得更容易。
当我们引入拥有大量函数和方法的三方工具或者框架时tree-shaking会变得很重要。想想lodash或者jQuery,如果我们只使用一个或者两个方法,就会因为加载其余内容而产生大量无用的开销。
Browserify和webpack就会包含大量无用的代码。但是Rollup不会 - 它只会包括我们真正用到的东西。
更新 (): 澄清一下,Rollup只能对ES模块上进行tree-shaking。CommonJS模块 - 像lodash和jQuery那样写的模块不能进行tree-shaking。然而,tree-shaking不是Rollup在速度/性能上唯一的优势。可以看和了解更多。
还有一个大新闻。
注意: 由于Rollup很高效,webpack 2 也将支持tree-shaking。
Part I: 如何使用Rollup处理并打包JavaScript文件
为了展示Rollup如何使用,让我们通过构建一个简单的项目来走一遍使用Rollup打包JavaScript的过程。
STEP 0: 创建一个包含将被编译的JavaScript和CSS的项目.
为了开始工作,我们需要一些用来处理的代码。这个教程里,我们将用一个小应用,可从获取。
目录结构如下:
learn-rollup/
├── src/
├── scripts/
├── modules/
├── mod1.js
└── mod2.js
└── main.js
└── styles/
└── main.css
└── package.json
你可以在终端执行下面的命令下载这个应用,我们将在这个教程中使用它。
# Move to the folder where you keep your dev projects.
cd /path/to/your/projects
# Clone the starter branch of the app from GitHub.
git clone -b step-0 --single-branch /jlengstorf/learn-rollup.git
# The files are downloaded to /path/to/your/projects/learn-rollup/
STEP 1: 安装Rollup并且创建配置文件。
第一步,执行下面的命令安装Rollup:
npm install --save-dev rollup
然后,在learn-rollup文件夹下新建rollup.config.js。在文件中添加如下内容。
export default {
entry: 'src/scripts/main.js',
dest: 'build/js/main.min.js',
format: 'iife',
sourceMap: 'inline',
说说每个配置项实际上做了什么:
entry — 希望Rollup处理的文件路径。大多数应用中,它将是入口文件,初始化所有东西并启动应用。
dest — 编译完的文件需要被存放的路径。
format — Rollup支持多种输出格式。因为我们是要在浏览器中使用,需要使用立即执行函数表达式(IIFE)[注1]
sourceMap — 调试时sourcemap是非常有用的。这个配置项会在生成文件中添加一个sourcemap,让开发更方便。
NOTE: 对于其他的format选项以及你为什么需要他们,看。
测试Rollup配置
当创建好配置文件后,在终端执行下面的命令测试每项配置是否工作:
./node_modules/.bin/rollup -c
在你的项目下会出现一个build目录,包含js子目录,子目录中包含生成的main.min.js文件。
在浏览器中打开build/index.html可以看到打包文件正确生成了。
完成第一步后我们的示例项目的状态。
注意:现在,只有现代浏览器下不会报错。为了能够在不支持ES2015/ES6的老浏览器中运行,我们需要添加一些插件。
看看打包出来的文件
事实上Rollup强大是因为它使用了“tree-shaking”,可以在你引入的模块中删除没有用的代码。举个例子,在src/scripts/modules/mod1.js中的sayGoodbyeTo()函数在我们的应用中并没有使用 - 而且因为它从不会被使用,Rollup不会将它打包到bundle中:
(function () {
'use strict';
* Says hello.
{String} name a name
* @return {String}
a greeting for `name`
function sayHelloTo( name ) {
const toSay = `Hello, ${name}!`;
return toS
* Adds all the values in an array.
{Array} arr an array of numbers
* @return {Number}
the sum of all the array values
const addArray = arr =& {
const result = arr.reduce((a, b) =& a + b, 0);
// Import a couple modules for testing.
// 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}`;
//# sourceMappingURL=data:application/charset=utf-8;base64,...
其他的构建工具则不是这样的,所以如果我们引入了一个像lodash这样一个很大的库而只是使用其中一两个函数时,我们的包文件会变得非常大。
比如使用的话,sayGoodbyeTo()也会打包进去,产生的打包文件比Rollup生成的大了两倍多。
STEP 2: 配置babel支持JavaScript新特性。
现在我们已经得到能在现代浏览器中运行的包文件了,但是在一些旧版本浏览器中就会崩溃 - 这并不理想。
幸运的是,已发布了。这个项目JavaScript新特性()到ES5, 差不多在今天的任何浏览器上都能运行。
如果你还没用过Babel,那么你的开发生涯要永远地改变了。使用JavaScript的新方法让语言更简单,更简洁而且整体上更友好。
那么让我们为Rollup加上这个过程,就不用担心上面的问题了。
INSTALL THE NECESSARY MODULES.
安装必要模块
首先,我们需要安装和适当的。
# Install Rollup’s Babel plugin.
npm install --save-dev rollup-plugin-babel
# Install the Babel preset for transpiling ES2015 using Rollup.
npm install --save-dev babel-preset-es2015-rollup
提示: Babel preset是告诉Babel我们实际需要哪些babel插件的集合。
创建.babelrc
然后,在项目根目录(learn-rollup/)下创建一个.babelrc文件。在文件中添加下面的JSON:
"presets": ["es2015-rollup"],
它会告诉Babel在转换时哪些preset将会用到。
更新rollup.config.js
为了让它能真正工作,我们需要更新rollup.config.js。
在文件中,importBabel插件,将它添加到新配置属性plugins中,这个属性接收一个插件组成的数组。
// Rollup plugins
import babel from 'rollup-plugin-babel';
export default {
entry: 'src/scripts/main.js',
dest: 'build/js/main.min.js',
format: 'iife',
sourceMap: 'inline',
plugins: [
exclude: 'node_modules/**',
为避免编译三方脚本,通过设置exclude属性忽略node_modules目录。
检查输出文件
全部都安装并配置好后,重新打包一下:
./node_modules/.bin/rollup -c
再看一下输出结果,大部分是一样的。但是有一些地方不一样:比如,addArray()这个函数:
var addArray = function addArray(arr) {
var result = arr.reduce(function (a, b) {
return a +
Babel是如何将(arr.reduce((a, b) =& a + b, 0))转换成一个普通函数的呢?
这就是编译的意义:结果是相同的,但是现在的代码可以向后支持到IE9.
注意: Babel也提供了,使得像Array.prototype.reduce()这些方法在IE8甚至更早的浏览器也能使用。
STEP 3: 添加ESLint检查常规JavaScript错误
在你的项目中使用linter是个好主意,因为它强制统一了代码风格并且能帮你发现很难找到的bug,比如花括号或者圆括号。
在这个项目中,我们将使用。
为使用ESLint,我们需要安装:
npm install --save-dev rollup-plugin-eslint
生成一个.eslintrc.json
为确保我们只得到我们想检测的错误,首先要配置ESLint。很幸运,我们可以通过执行下面的命令自动生成大多数配置:
$ ./node_modules/.bin/eslint --init
? How would you like to configure ESLint? Answer questions about your style
? Are you using ECMAScript 6 features? Yes
? Are you using ES6 modules? Yes
? Where will your code run? Browser
? Do you use CommonJS? No
? Do you use JSX? No
? What style of indentation do you use? Spaces
? What quotes do you use for strings? Single
? What line endings do you use? Unix
? Do you require semicolons? Yes
? What format do you want your config file to be in? JSON
Successfully created .eslintrc.json file in /Users/jlengstorf/dev//projects/learn-rollup
如果你按上面展示的那样回答问题,你将在生成的.eslintrc.json中得到下面的内容:
"browser": true,
"es6": true
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
"rules": {
"indent": [
"linebreak-style": [
"quotes": [
修改.eslintrc.json
然而我们需要改动两个地方来避免项目报错。
使用2空格代替4空格。
后面会使用到ENV这个全局变量,因此要把它加入白名单中。
在.eslintrc.json进行如下修改 — 添加globals属性并修改indent属性:
"browser": true,
"es6": true
"globals": {
"ENV": true
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
"rules": {
"indent": [
"linebreak-style": [
"quotes": [
更新rollup.config.js
然后,引入ESLint插件并添加到Rollup配置中:
// Rollup plugins
import babel from 'rollup-plugin-babel';
import eslint from 'rollup-plugin-eslint';
export default {
entry: 'src/scripts/main.js',
dest: 'build/js/main.min.js',
format: 'iife',
sourceMap: 'inline',
plugins: [
exclude: 'node_modules/**',
exclude: [
'src/styles/**',
检查控制台输出
第一次,当执行./node_modules/.bin/rollup -c时,似乎什么都没发生。因为这表示应用的代码通过了linter,没有问题。
但是如果我们制造一个错误 - 比如删除一个分号 - 我们会看到ESLint是如何提示的:
$ ./node_modules/.bin/rollup -c
/Users/jlengstorf/dev//projects/learn-rollup/src/scripts/main.js
Missing semicolon
? 1 problem (1 error, 0 warnings)
一些包含潜在风险和解释神秘bug的东西立刻出现了,包括出现问题的文件,行和列。
但是它不能排除我们调试时的所有问题,很多由于拼写错误和疏漏产生的bug还是要自己花时间去解决。
STEP 4: 添加插件处理非ES模块
如果你的依赖中有任何使用Node风格的模块这个插件就很重要。如果没有它,你会得到关于require的错误。
添加一个Node模块作为依赖
在这个小项目中不引用三方模块很正常,但实际项目中不会如此。所以为了让我们的Rollup配置变得真正可用,需要保证在我们的代码中也能引用是三方模块。
举个简单的例子,我们将使用包添加一个简单的日志打印器到项目中。先安装它:
npm install --save debug
注意:因为它是会在主程序中引用的,应该使用--save参数,可以避免在生产环境下出现错误,因为devDependencies在生产环境下不会被安装。
然后在src/scripts/main.js中添加一个简单的日志:
// 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');
// Enable the logger.
debug.enable('*');
log('Logging is enabled!');
// 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时会得到一个警告:
$ ./node_modules/.bin/rollup -c
Treating 'debug' as external dependency
No name was provided for external module 'debug' in options.globals – guessing 'debug'
而且如果在查看index.html,会发现一个ReferenceError抛出了:
默认情况下,三方的Node模块无法在Rollup中正确加载。
哦,真糟糕。完全无法运行。
因为Node模块使用,无法与Rollup直接兼容。为解决这个问题,需要添加一组处理Node模块和CommonJS模块的插件。
围绕这个问题,我们将在Rollup中新增两个插件:
,运行加载node_modules中的三方模块。
,将CommonJS模块转换成ES6,防止他们在Rollup中失效。
通过下面的命令安装两个插件:
npm install --save-dev rollup-plugin-node-resolve rollup-plugin-commonjs
更新rollup.config.js.
然后,引入插件并添加进Rollup配置:
// 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';
export default {
entry: 'src/scripts/main.js',
dest: 'build/js/main.min.js',
format: 'iife',
sourceMap: 'inline',
plugins: [
jsnext: true,
main: true,
browser: true,
commonjs(),
exclude: [
'src/styles/**',
exclude: 'node_modules/**',
注意: jsnext属性是为了帮助的一部分。main和browser 属性帮助插件决定哪个文件应该被bundle文件使用。
检查控制台输出
执行./node_modules/.bin/rollup -c重新打包,然后再检查浏览器输出:
成功了!日志现在打印出来了。
STEP 5: 添加插件替换环境变量
环境变量使开发流程更强大,让我们有能力做一些事情,比如打开或关闭日志,注入仅在开发环境使用的脚本等等。
那么让Rollup支持这些功能吧。
在main.js中添加ENV变量
让我们通过一个环境变量控制日志脚本,让日志脚本只能在非生产环境下使用。在src/scripts/main.js中修改log()的初始化方式。
// 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!');
debug.disable();
然而,重新打包(./node_modules/.bin/rollup -c)后检查浏览器,会看到一个ENV的ReferenceError。
不必惊讶,因为我们没有在任何地方定义它。如果我们尝试ENV=production ./node_modules/.bin/rollup -c,还是不会成功。因为那样设置的环境变量只是在Rollup中可用,不是在Rollup打包的bundle中可用。
我们需要使用一个插件将环境变量传入bundle。
安装插件,它本质上只是做了查找-替换的工作。它能做很多事情,但现在我们只需要让它简单地找到出现的环境变量并将其替换成实际的值。(比如,所有在bundle出现的ENV变量都会被替换成"production" )。
npm install --save-dev rollup-plugin-replace
更新rollup.config.js
在rollup.config.js中引入插件并且添加到插件列表中。
配置非常简单:只需添加一个键值对的列表,key是将被替换的字符串,value是应该被替换成的值。
// 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';
export default {
entry: 'src/scripts/main.js',
dest: 'build/js/main.min.js',
format: 'iife',
sourceMap: 'inline',
plugins: [
jsnext: true,
main: true,
browser: true,
commonjs(),
exclude: [
'src/styles/**',
exclude: 'node_modules/**',
exclude: 'node_modules/**',
ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
在我们的配置中,将找打所有出现的ENV并且替换成process.env.NODE_ENV - 在Node应用中最普遍的设置环境变量的方法 - 或者 "development"中的一个。使用JSON.stringify()确保值被双引号包裹,如果ENV没有的话。
为了确保不会和三方代码造成问题,同样设置exclude属性来忽略node_modules目录和其中的全部包。(幸亏。)
首先,重新打包然后在浏览器中检查。控制台日志会显示,就像之前一样。很棒 - 这意味着我们的默认值生效了。
为了展示新引入的能力,我们在production模式下运行命令:
NODE_ENV=production ./node_modules/.bin/rollup -c
注意: 在Windows上,使用SET NODE_ENV=production ./node_modules/.bin/rollup -c防止在设置环境变量时报错。
当刷新浏览器后,控制台没有任何日志打出了:
不改变任何代码的情况下,使用一个环境变量禁用了日志插件。
STEP 6: 添加UglifyJS压缩减小生成代码体积
这个教程中最后一步是添加UglifyJS来减小和压缩bundle文件。可以通过移除注释,缩短变量名和其他压缩换行等方式大幅度减少bundle的大小 - 会让文件的可读性变差,但提高了网络间传输的效率。
我们将使用压缩bundle,通过插件。
通过下面命令安装:
npm install --save-dev rollup-plugin-uglify
更新rollup.config.js
然后添加Uglify到Rollup配置。为了开发环境下可读性更好,设置代码丑化仅在生产环境下使用:
// 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';
export default {
entry: 'src/scripts/main.js',
dest: 'build/js/main.min.js',
format: 'iife',
sourceMap: 'inline',
plugins: [
jsnext: true,
main: true,
browser: true,
commonjs(),
exclude: [
'src/styles/**',
exclude: 'node_modules/**',
ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
(process.env.NODE_ENV === 'production' && uglify()),
我们使用了,很常用(虽然)的条件性设置值的方法。[注4]
在我们的例子中,只有在NODE_ENV是"production"时才会加载uglify()。
检查压缩过的bundle
保存配置文件,让我们在生成环境下运行Rollup:
NODE_ENV=production ./node_modules/.bin/rollup -c
注意: 在Windows上,使用SET NODE_ENV=production ./node_modules/.bin/rollup -c防止在设置环境变量时报错。
输出内容并不美观,但是更小了。这有build/js/main.min.js的截屏,看起来像这样:
丑化过的代码确实能更高效地传输。
之前,我们的bundle大约42KB。使用UglifyJS后,减少到大约29KB - 在没做其他优化的情况下节省了超过30%文件大小。
接下来的内容
在这个系列的下一节,我们将了解通过Rollup和处理样式,并且添加live reloading来实时看见我们的修改。
Further Reading
- 这篇文章让我开始对Rollup感兴趣,因为它展示了一些Rollup相比webpack和Browserify的优势。
注1: 这是一个非常难理解的概念,所以没全理解也不要有压力。简单来说,我们希望我们的代码在他们自己的作用域中,防止和其它脚本的冲突。IIFE是一个包括我们的代码在自身作用域的一个[闭包]。
注2:It’s important to keep in mind, though, that when we’re dealing with such a small example app it doesn’t take much to double a file size. The comparison at this point is ~3KB vs. ~8KB.
注3:作为曾经花数小时找bug然后发现拼错一个变量名的人,不需要夸大使用linter带来的效率提升。
注4:举个例子,使用这种方法来赋默认值时非常常见的。(比如var foo = maybeThisExists || 'default';)
这篇文章的代码放在GitHub上。你可以进行修改或测试,或者报告bug,或者进行建议或者修改。
4 收藏&&|&&17
你可能感兴趣的文章
2 收藏,592
6 收藏,1.1k
本文翻译的只是原文作者在start kit下的实践,因此涉及的问题不全面,我也没额外了解更多。试着Google了一下关键字rullup multi,多个entry的情况貌似通过插件完成的,详见,其实我个人认为用Rollup来打包library更适合,哈哈。
本文翻译的只是原文作者在start kit下的实践,因此涉及的问题不全面,我也没额外了解更多。试着Google了一下关键字`rullup multi`,多个entry的情况貌似通过插件完成的,详见[rollup-plugin-multi-entry](/rollup/rollup-plugin-multi-entry),其实我个人认为用Rollup来打包library更适合,哈哈。
请问windows 下如何执行命令./node_modules/.bin/rollup -c
请问windows 下如何执行命令./node_modules/.bin/rollup -c
多个entry如何处理呢?
多个entry如何处理呢?
平时windows很少用,我在windows下使用git bash来执行终端命令,也可以将rollup这个模块直接安装到全局npm install -g rollup后直接使用rollup -c。
平时windows很少用,我在windows下使用git bash来执行终端命令,也可以将rollup这个模块直接安装到全局`npm install -g rollup`后直接使用`rollup -c`。
分享到微博?
我要该,理由是:在 SegmentFault,解决技术问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。
一线的工程师、著名开源项目的作者们,都在这里:
获取验证码
已有账号?
标签:至少1个,最多5个
本文原始来源:。转载请提供原始来源,谢谢!
前阵子为了满足工作上的一个需求开发了一个PostCSS 插件,后来也将这个插件提交给PostCSS 官方并得到认可。在这篇文章中笔者将记录开发过程中遇到的一些问题,且斗胆将之称为“最佳实践”,希望对有兴趣尝试PostCSS 插件开发的您有所帮助。
开发成果展示
首先先上成果: (欢迎给个star 哦~)
postcss-lazyimagecss 插件实现的功能是为 CSS 中的background-image 对应的图片自动添加width 与height 属性。简单形象化的效果展示如下:
/* Input ./src/index.css */
.icon-close {
background-image: url(../slice/icon-close.png); //icon-close.png - 16x16
.icon-new {
background-image: url(../slice/icon-new@2x.png); //icon-new@2x.png - 16x16
/* Output ./dist/index.css */
.icon-close {
background-image: url(../slice/icon-close.png);
height: 16
.icon-new {
background-image: url(../slice/icon-new@2x.png);
background-size: 8px 8
为什么重复造一个轮子
开发这个PostCSS 插件的起因是原先工作流中使用的 插件在加入SourceMap 功能后运行不正常,多次尝试修复均告失败。后来笔者想到,PostCSS 本身天然支持SourceMap,那如果将这个功能开发成PostCSS 插件岂不是也完美支持SourceMap 了?
于是笔者便在 的基础上开发出了这么一个轮子。在此也感谢原开发者 与 的大力帮助与支持。对笔者而言,更像是站在巨人的肩膀上开发出来这个插件。
关于PostCSS 的原理,官方有这么一个图:
简单解释,PostCSS 会将上一步传入的 CSS 按照一条条样式规则(rule)进行解析(Parser)得到一个节点树;然后借助一系列插件在节点树上进行转换操作,并最终通过Stringifier 进行拼接。source map则记录了前后的对应关系。
当然,在实际的开发中其实不必深究原理,最重要的是看来调用即可。
工欲善其事必先利其器
开发一个PostCSS 插件也是开发一个Node 模块,想到后面要发布到NPM 跟PostCSS 官方,那么作为一个开源项目的可维护性、可扩展性也是很重要的。因此在进入正式的开发之前,笔者做了如下的工作:
1、配置 editorconfig
作为一套统一代码格式的解决方案,已经在团队不少项目中使用,其很好地解决了因为团队协作中因不同代码编辑器及不同的代码习惯产生的潜在风险。这里是。
2、基础的开发工作流
在整个开发插件过程前,笔者根据需求配了个基于Gulp 的开发工作流,主要配备如下功能(任务):
代码质量监控ESlint
优秀的开源代码必然是有着标准化的JavaScript 代码风格,因此在整个开发过程中借助ESlint 来严格控制自己的代码质量。是本项目的ESlint 配置文件。
var eslint = require('gulp-eslint');
gulp.task('lint', function () {
return gulp.src(files)
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError());
基础的CSS 转换
这个任务其实就是本PostCSS 插件实现的功能,之所以在开发过程中也要配置是为了下面的单元测试任务的调用。
秉承TDD(测试驱动开发)的开发理念,单元测试的任务是必不可少的。
gulp.task('test', function () {
return gulp.src('test/*.js', { read: false })
.pipe(mocha({ timeout: 1000000 }));
watch 任务
gulp watch 任务是上面任务的集体调用,实现的功能是在开发过程中,每当按下保存键就自动运行ESlint 代码质量监控及进行单元测试任务。有效保障了整个开发过程中的质量。
3、托管到 Github 并配置Travis-ci 持续集成
整个开发过程使用Github 托管源代码并通过Travis-ci 持续集成。PostCSS 官方建议最低需要支持Node.js 0.12 的版本,所以整个Travis-ci 的配置文件如下:
sudo: false
language: node_js
- "stable"
before_script:
- npm install -g mocha
相应的在Travis-ci 管理后台配置push 操作作为动作钩子,这样每次有commit push 上去就会自动进行测试并在log 上展示出结果:
从最小开始
一个PostCSS 插件最基础的构成如下:
var postcss = require('postcss');
module.exports = postcss.plugin('PLUGIN_NAME', function (opts) {
opts = opts || {};
// 传入配置相关的代码
return function (root, result) {
// 转化CSS 的功能代码
然后就是不同的需求情况来决定是否引入第三方模块,是否有额外配置项,然后在包含root,result 的匿名函数中进行最为核心的转换代码功能编写。
root(css),rule, nodes, decl, prop, value
如本文一开头的PostCSS 原理解析,CSS 文件在经过Parser 转化后的递归单个子单位可以归为如下:
root(css) :也是整个CSS 代码段,包含多个rule。
rule: 包含一个CSS class 范围内的代码段
.icon-close {
background-image: url(../slice/icon-close.png);
font-size: 14
nodes: 代指rule 中{}中间的多个 decl 部分。
decl: 单行CSS ,即有属性与值的部分
background-image: url(../slice/icon-close.png);
prop,value
相应的CSS 属性与值,如上面 prop为background-image,value为url(../slice/icon-close.png)
伪代码实现
根据postcss-lazyimagecss 插件要实现的内容,涉及到CSS 转化的有如下情景:
增加 width 属性及获取到真实值
增加 height 属性及获取到真实值
二倍图情况下增加 background-size 属性并计算出值
结合上一小节,可以先写出如下简洁版伪代码:
css.walkRules(function (rule) { // 遍历所有 CSS
rule.walkDecls(/^background(-image)?$/, function (decl) { // 遍历每条 CSS 规则,找出目标 rule
// 一些传参等代码
nodes.forEach(function (node) { // 遍历其它 rules
... // 其它代码实现,如找出图片真实width 等
rule.append({prop: 'width', value: valueWidth}); // 在该decl 追加width 属性
接下来就是考虑不同情况增加一些逻辑判断:
判断url 中是否为网络地址或Base64 的data 形式:imageRegex.exec(value).indexOf('data:')
判断该rule 下是否已经有width 等属性,在nodes 循环中:
if (node.prop === 'width') {
CSSWidth =
判断2倍图图片宽高是否为偶数:
value.indexOf('@2x') & -1 && (info.width % 2 !== 0 || info.height % 2 !== 0
再具体的不再详述,完整的代码实现可以。
postcss-lazyimagecss 插件使用了第三方模块 来进行图片数据(文件类型、宽高)的获取,大大提高了开发效率。然而在寻找图片绝对路径的这个实现上还是绕了不少弯路。
插件的思路是需要获取CSS 中background-image属性对应值中url()的相对图片路径,以此来找到图片的绝对路径,之后用fast-image-size 模块获取到相应的数据。
然而在一些特殊情况并不能准确找到绝对路径。
在CSS 预处理器(如Less 或Sass)中,常借助@import来组件化CSS 代码,然而在层层@import 下路径可能已经被产生变化。举个例子,有如下结构:
├── css
├── html
├── img
└── icon.png
└── scss
├── index.scss
└── second
└── _import.scss
上面的文件树中展示的 scss/index.scss @import 了二级目录下的 _import.scss,在_import.scss中有一个类需要用到img/icon.png。
因为同时也配置了local server(以上面的./目录作为server 的根目录),那么在 url 中可以写成../../img/icon.png 或../img/icon.png,甚至写成../../../../../img/icon.png(N个../)——这些情况下Sass 编译后的index.css 均可正常读取。原因相信也知道,因为root url的存在,上面的路径写法均相当于/img/icon.png。
在这个情况下于用户而言是感受不到错误的,但在插件中可就找不到真实绝对路径了。笔者对于这个情况是采用了如下方式进行解决:
借助Node.js 中的fs.existsSync 函数检测绝对路径对应的文件是否存在。第一次为正常fs.existsSync,如果找到就跳出;如果没有则先对路径的字符串执行replace('../', '');然后再次执行fs.existsSync。如果两次均没有找到则在终端进行提示,但这种情况下并不会报错破坏进程的运行。
function fixAbsolutePath(dir, relative) {
// find the first time
var absolute = path.resolve(dir, relative);
// check if is a image file
var reg = /\.(jpg|jpeg|png|gif|svg|bmp)\b/i;
if (!reg.test(absolute)) {
pluginLog('Not a image file: ', absolute);
if (!fs.existsSync(absolute) && (relative.indexOf('../') & -1)) {
relative = relative.replace('../', '');
// find the second time
absolute = path.resolve(dir, relative);
不敢说这是一种最好的处理方式,但至少是一种可行的处理方式。
单元测试上采用Mocha 测试工具, should.js 做断言库。在笔者看来,结合TDD 进行开发,单元测试仅作为一种开发的辅助手段,规避开发过程中一些产生致命的报错。本文不展开如何写单元测试,具体实现可点击。
在Postcss 官方Github Repo,有一个。对于其提倡的“Do one thing, and do it well” 深感认同,因此在基本完成插件功能后笔者又做了如下优化工作。
更友好的log 提示
官方其实是建议用内置的result.warn来代替console.log或console.warn来展示log 信息(原因据说是一些PostCSS 处理器会忽略这类console log 输出)。不过笔者尝试后发现官方函数下提示的信息会非常长,后面采用了借助chalk 模块封装了console.log的形式增加了高亮态信息展示。
“找不到图片文件”的场景处理
用户在写CSS 代码的时候,background-image 的url 可能会有如下情况:
输入的是目录
输入的非图片路径
输入了一半就保存了
根本就是瞎输入
场景很多,但对于插件而言仅仅是能否找到与否的结果。在处理这些错误场景的情况下也给出的细分到“File does not exist” 或 “Not a image file”的情况,让这类错误提醒更加友好一些。
提示二倍图不正确
如果用户引用的二倍图(类似xxx@2x.png)的宽度高度为非偶数的话,也会有相应的提醒。
以上的报错提示在实际运行效果如下:
英文版 README
PostCSS 官方建议是README.md用英文写,其余语种采用类似README.zh.md的方式。
维护一份 changelog
按照建议,也将更新历史等数据放在了一个名为CHANGELOG.md文件上,并采用。
根据自己的开发习惯,在Github 上的Repo 也放置了一份LICENSE 文件。
发布到NPM 官方
发布到NPM 官方的步骤在这里就不再详述。仅分享一个不错的版本号增加方式(告别packup.json 的手动改版本数字)。
npm version patch =& z+1
npm version minor =& y+1 && z=0
npm version major =& x+1 && y=0 && z=0
与上文所讲的相关,vX.Y.Z(主版本号.次版本号.修订号)三个选项分别对应三部分的版本号,每次运行命令会导致相应的版本号递增一,同时子版本号清零。记得运行上面命令前先将文件变动提交到git 上去。
之后运行npm publish命令即可。
发布到PostCSS 官方
Postcss 官方主页上有个 文件展示了所有的第三方插件,提交的话Fork 一份然后在该文件增加自己的插件详细然后提交合并,等作者允许即可。
发布到postcss.part
是一个非官方的PostCSS 插件搜索平台。提交自己插件可按照这个。其实本质也是Fork 然后加信息在Pull request 的方式,在此不累述。
在开发完postcss-lazyimagecss 插件后,笔者按照上面的发布方式提交了给官方。后面效果还不错,PostCSS 作者也提了个star 跟。PostCSS 官方推特上的推荐也带来了第一批Stargazers。
因为这个缘故,在第三届中国CSS 大会上也有幸与PostCSS 作者ai 大神勾搭了下,并得到了大神赠送的俄罗斯巧克力。
在笔者看来,PostCSS 的作为一个CSS 转换引擎,其不参与细分功能实现仅交于第三方插件的设计理念,让其产生了一个非常的开放的生态。但对于个开放机制下的一些情况笔者并不是很赞同,如一些用中文写CSS 的插件(当然这个更多是for fun),一些自定义CSS 属性如用size: 10px 2px 等代替width/height的插件——在笔者看来PostCSS 插件应该更多在遵从CSS 标准语法的基础上进行扩展。
但无论如何,还是挺佩作者开发出了这么个造福前端届的工具;也因为认同作者,笔者写了这篇文章为推广PostCSS 做了一点微小的工作;也希望对看到文末的您有所帮助,积极参与到开源创作的事业中。
参考文章:
2 收藏&&|&&10
你可能感兴趣的文章
2 收藏,210
6 收藏,1.1k
35 收藏,3k
本作品 保留所有权利 。未获得许可人许可前,不允许他人复制、发行、展览和表演作品。不允许他人基于该作品创作演绎作品
width和height可以自定义吗?如果是固定了1倍8px,2倍16px,就不方便了。。
width和height可以自定义吗?如果是固定了1倍8px,2倍16px,就不方便了。。
如果该段rule 用户已经写了width 或height 属性是不会产生的
如果该段rule 用户已经写了width 或height 属性是不会产生的
这样的话使用范围就太小了,我通常都是用rem、em、%、vh来表示,很少用到px
这样的话使用范围就太小了,我通常都是用rem、em、%、vh来表示,很少用到px
如果按照这个使用条件那你是不符合的哟;另外也可以说借助其它插件额外实现px to rem/em 的操作而非因为这个而局限自己,不是吗?
如果按照这个使用条件那你是不符合的哟;另外也可以说借助其它插件额外实现px to rem/em 的操作而非因为这个而局限自己,不是吗?
分享到微博?
我要该,理由是:

我要回帖

更多关于 ps样式打包 的文章

 

随机推荐