微博登录后即可参与讨论。
season.chen

前端构建工具漫谈,fis3、webpack、rollup.js 23个回复 专栏 @ FIS

season.chen 发布于 9 月前

转自本人知乎原文

注:本人是FIS重度使用者,纯属以分享的心态推广FIS3,可能会对FIS更为赞赏,但与FIS3研发团队没有半毛钱关系。也没有贬低webpack或者其他工具的意思,如有观点不准确或有失偏颇,权当理解为本人偏见。

本文涉及三种前端构建工具:FIS3,Webpack,Rollup.js

FIS3: 百度前端团队作品,在百度的各个产品线的实践中演化而来。从当初的FIS到FIS2,再到如今的FIS3,在设计理念和使用体验上都翻了翻,不但具备了Webpack等工具的所有功能,还有许多理念都属前沿,是名符其实的前端工程化工具。

FIS3 , 为你定制的前端工程构建工具。解决前端开发中自动化工具、性能优化、模块化框架、开发规范、代码部署、开发流程等问题

Webpack:貌似是目前使用最为火热的打包工具,各大知名的框架类库都用其打包,国内使用最近也火热起来。它在单页应用和类库打包上帮助许多人从代码管理中解脱了出来,成为了当下风靡一时的打包工具。

A bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows to load parts for the application on demand. Through "loaders" modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.

Rollup.js:号称为下一代的模块打包工具,是一个比较新的工具,但是使用人数不少。它采用一个据称为shaking tree的技术,利用es6模块能静态分析语法树的特性,只将需要的代码提取出来打包,能大大减小代码体积。可以预见,未来的各种框架类库都会采用es6语法编写,它在未来的有种不菲的潜力。但据说webpack2也增加了类似的功能,孰优孰劣不甚清楚。

Rollup is a next-generation JavaScript module bundler. Author your app or library using ES2015 modules, then efficiently bundle them up into a single file for use in browsers and Node.js – even if you use advanced features like bindings and cycles.

一、理念

webpack:从其简介就可以看出,它一开始的定位是‘模块打包器’,通过定义entry js,对所有依赖的文件进行跟踪,将各个模块通过过着loader和plugins处理,然后打包在一起。打包过程中,可以将文件分为多个chunks,或者提取公共文件提取出来作为commonChunk。

在webpack里,一切根据entry js进行处理,其他的css、图片等等都是附属品。可以说,它在单页应用和类库打包方面的确使用非常方便。但是一到了多页应用,就会遇到一些问题,后续会有介绍。

FIS3:真正从前端工程化的角度设计,不单单是一个打包工具。在FIS3中,js,image,html,css等等,不管你是谁,只要是前端相关的资源都是主角。构建流程不单单根据js来,而是分析每一个文件的依赖关系,生成一个资源表‘sourceMap’,资源表记录每一个文件的依赖关系,以及发布前的位置,和发布后的去向。

FIS3并没有entry的概念,你可以根据内置打包阶段生成的资源表,自己定义各种自定了的打包工具‘postpackager’,或者称之为资源Loader。你可以根据资源表定义任何的产出规则,是根据js来?还是根据html来?或者是各自独立打包?只要你想得到,就能实现……

二、流程

客观来说,webpack的流程并不是很清晰,总的来说便是根据entry为入口,搜集依赖的过程中会调用各自Loaders,同时也通过各自plugins进行辅助打包。插件功能一朵,你会有点不清楚它每个阶段具体干了写什么,各个插件还可能穿插混淆。

相对来说,FIS3的整个构建流程一目了然。它明确地将构建流程分为了单文件编译 和 打包 两个阶段。而单文件编译阶段又分为lint(代码审查)、parser(编译)、preprocessor(fis内置标准前处理)、standard(fis内置标准处理)、postprocessor(fis内置语法标准处理后)、optimizer(代码优化)六个阶段。 打包又分为:prepackager(打包前处理)、packager(标准打包阶段)、spriter(图片合并等)、postpackager(打包后处理)四个阶段。

FIS3编译流程

FIS3的每个阶段分工明确,职责清晰,让你能清楚地知道项目构建的过程中每一步干了些什么。每个阶段可以编写对应的各种插件,独自出来自己的那部分事情。打包阶段,你能知道整个项目的资源列表以及每个文件的依赖关系。

三、配置

Webpack的配置大致分为:entry、output、plugins、module、resolve等等等,配置文件大致是这样的:

module.exports = {
    //插件项
    plugins: [commonsPlugin],
    //页面入口文件配置
    entry: {
        index : './src/js/page/index.js'
    },
    //入口文件输出配置
    output: {
        path: 'dist/js/page',
        filename: '[name].js'
    },
    module: {
        //加载器配置
        loaders: [
            { test: /\.css$/, loader: 'style-loader!css-loader' },
            { test: /\.js$/, loader: 'jsx-loader?harmony' },
            { test: /\.scss$/, loader: 'style!css!sass?sourceMap'},
            { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
        ]
    },
    //其它解决方案配置
    resolve: {
        root: 'E:/github/flux-example/src', //绝对路径
        extensions: ['', '.js', '.json', '.scss'],
        alias: {
            AppStore : 'js/stores/AppStores.js',
            ActionType : 'js/actions/ActionType.js',
            AppAction : 'js/actions/AppAction.js'
        }
    }
};

而fis3的配置文件,是通过glob语法match到某些文件,然后对这些文件定义各自的各个阶段的插件处理、发布规则,而且还能通过media定义不同环境的发布规则。配置文件大致是这样的:

var path = require('path');
var domain = '';

// 需要构建的文件
fis.set('project.fileType.text', 'po');
fis.set('project.files', ['src/**']);
fis.set('project.ignore', fis.get('project.ignore').concat(['demo/**', 'DS_store',]));

// 模块化支持插件
// https://github.com/fex-team/fis3-hook-commonjs (forwardDeclaration: true)
fis.hook('commonjs', {
    extList: ['.js', '.coffee', '.es6', '.jsx'],
    umd2commonjs: true
});

// 模块文件,会进行require包装
fis.match('/{node_modules,src}/**.{js,jsx}', {
  isMod: true,
  useSameNameRequire: true
});

// 不是AMD、UMD或者CMD规范的
fis.match('src/scripts/{engine,plugin,shim}/**', {
  isMod: false
});

// 所有文件
fis.match('src/(**)', {release: 'assets/$1'});

// html
fis.match('src/page/(**)', {release: 'page/$1'});

// node_modules
fis.match('node_modules/(**)', {release: 'npm/$1'});

// 所有js, jsx
fis.match('src/**.{js,jsx}', {
  rExt: 'js',
  useSameNameRequire: true,
  parser: fis.plugin('babel-5.x', {}, {
    presets: ["es2015", "react", "stage-0",]
  })
});

// 处理语言文件*.po
fis.match('src/**.po', {
  rExt: '.js',
  isMod: true,
  isJsLike: true,
  parser: fis.plugin('po', {
    //
  })
});

// page js not mod
fis.match('src/scripts/page/**.{js,jsx}', {isMod: false});
fis.match('src/demo/js/message.js', {isMod: false});

// 不是es6和react模块的文件
fis.match('src/scripts/{engine,vendor,plugin,shim}/**', {
  parser: null
});
fis.match('src/demo/js/webim/easemob*.*', {
  parser: null
});

// 用 less 解析
fis.match('*.less', {
  rExt: 'css',
  parser: [fis.plugin('less-2.x')],
  postprocessor: fis.plugin('autoprefixer')
});

// 添加css和image加载支持
fis.match('*.{js,jsx,ts,tsx,es}', {
  preprocessor: [
    fis.plugin('js-require-css'),
    fis.plugin('js-require-file', {
      useEmbedWhenSizeLessThan: 10 * 1024 // 小于10k用base64
    }),
  ]
});

// 用 loader 来自动引入资源。
fis.match('::package', {
  postpackager: fis.plugin('loader')
});

// 禁用components
fis.unhook('components');
fis.hook('node_modules');

// demos
fis.match('src/demo/(**)', {
  release: 'demo/$1'
});

// 部署:=====
// local: 本地环境
fis
  .media('local')
  .match('**.{js,jsx,css,less}', {
    useHash: false
  })
  .match('src/demo/(**)', {
    release: 'demo/$1'
  })
  .match('**', {
    deploy: fis.plugin('local-deliver', {
      to: path.join(__dirname, './local/')
    })
  });

// test: 测试环境
fis
  .media('test')
  .match('**.{js,jsx,css,less}', {
    useHash: true
  })
  .match('src/demo/(**)', {
    release: false
  })
  // .match('*.js', {
  //   optimizer: fis.plugin('uglify-js')
  // })
  // .match('*.{css,less,styl}', {
  //   optimizer: fis.plugin('clean-css')
  // })
  .match('**', {
    deploy: fis.plugin('local-deliver', {
      to: path.join(__dirname, './test/')
    })
  });

// prod: 正式环境
fis
  .media('prod')
  .match('**.{js,jsx,css,less}', {
    useHash: true
  })
  .match('src/demo/(**)', {
    release: false
  })
  .match('*.js', {
    optimizer: fis.plugin('uglify-js')
  })
  .match('*.{css,less,styl}', {
    optimizer: fis.plugin('clean-css')
  })
  .match('**', {
    deploy: fis.plugin('local-deliver', {
      to: path.join(__dirname, './prod/')
    })
  });

相对来说,fis3的配置,更加优雅,可操控性更强。什么环境,那些文件在什么阶段使用什么插件,在哪个阶段会发生些什么事情,一目了然。

四、输出

Webpack一切都跟着entry js走,最后将一个entry的所有依赖打包进去。可以通过插件将打包文件chunk为多个,也可以将多个entry文件的依赖的公共部分进行‘common chunk’。

这在单页应用和类库打包中发挥还算不错,可是一碰到多页应用就有点力不从心。不管你如何chunk,总不能真正达到按需加载的地步,你往往要去考虑如何提取公共文件才能达到最优状态。

FIS3,打包阶段只搜集所以文件的列表以及各种的依赖关系,并没有entry的限制。你可以根据打包阶段生成的资源表,自定义产出规则。当然也有现成的最为常用的打包插件,比如,fis3常用的‘fis-postpackager-loader’,便是以html为entry文件,读取每一个html文件的所有依赖,将他们写入html中,你可以让他们打包成一个或多个文件,或者根本不打包。

还有,FIS3能正在实现在多页应用中按需加载,每个页面只需要加载自己依赖的所有文件即可。

可以说,你完全可以根据资源表自由发挥,自己写一个‘fis3-postpackager-entry’插件,以js为入口进行产出,便能地实现webpack的所有功能。

另外,你可以为任何一个文件指定packTo属性,告诉他需要打包去哪里,相对于webpack的chunk,更为灵活。

五、路径和文件指纹

Webpack的产出路径都依赖js的产出目录,为了便于维护,你通常样指定各种比较规范的产出目录结构。

不知道大家是怎么在html中引用生成的js和css,image等各种图片的,我知道的方法便是预先定义好产出目录位置,然后在根据那个位置写引用路径。

FIS3处理大不相同。任何时候,在编写代码阶段,你只需要关心编写代码的时候文件的路径,而不用关心发布后它的去向。

在发布的时候,任何一个文件可以根据配置发布到任何一个位置,文件中相应的路径自动会替换成发布后的路径。你也可以在资源列表中了解每一个文件发布前和发布后的路径对应关系。

文件指纹:FIS3能对任何文件使用hash控制,在引用它的任何地方的路径会被自动替换为hash路径。配置也很简单:

fis.match('src/test/**.js', {
  useHash: true
});

六、依赖

fis不但能通过require,import等查找依赖,而且会搜寻src,href等各处与资源定位有关的地方,将他们计入依赖列表。

七、产出文件格式

webpack产出的文件,将各个模块计入一个全局的webpackjson文件,通过内置webpack_require对各个模块进行引用。这意味着你只能将产出的文件作为一个独立的模块调用,不能引用其中的子模块。

FIS3采用require格式,只是将各个模块通过define转换为CMD模块,合并在一起。你可以单独引用其中的一个模块,但是必须配合require.js、mod.js等模块引擎一起使用。

八、发展和生态

Webpack使用广泛,github上star数达到15000+,各种热门框架都有其插件,比如react-loader,vue-loader,vue-hot-loader等等。

fis3相比起来就有些惨淡,start书才1000+,有些热门框架找不到对应插件。比如前段时间使用vue.js进行组件化开发时,因为找不到对应parser插件,只能将一个vue组件拆分成css和html,而不能像webpack里使用独立文件的方式使用。

Webpack使用的server插件提供hot load的功能更,react、vue等都提供了热加载插件。FIS3内置就有一个server,暂时貌似并没有热加载api,不过我认为这并不是我必须的功能。

九、个人认为FIS3推广缓慢的原因

FIS3如此优秀,却有些默默无闻的味道,究其原因,想到如下几点:

1、可能是国内的技术氛围普遍落后于国外,国内的开发者们包括我自己,往往都贡献精神较差。往往觉得工具提供者为自己服务是理所当然的事,遇到问题就闷头抱怨提问,不愿贡献pr去解决问题,贡献代码。往往有这种情况,同一个问题能在同一个项目在同时存在几十个相同issue的情况……

2、因为是中文项目,国外的人很难入手,而国内的人大都喜欢国外流行的技术,不愿去了解和尝试国内一个不怎么火热的工具。

3、FIS3为百度团队开发。可能开发者的初衷主要是为方便自己内部使用,没有很强烈的推广心态。还有,就是可能他们手上的项目是在太多,精力不够,在各种pr的处理上没有那么及时。去看看webpack、vue等项目,往往issue都能被很快的给解决,open的issue也被明确地标上各种tag。而在百度的各个项目里,常常存在着许多无意义的issue状态是open,有很多确实是问题的issue都无疾而终,比如webuploader的一个removeFile的issue,同样的问题竟然同时存在十大几个,一个也未找到答案……

4、功能强大,也意味着进入门槛稍高,要完全搞懂掌握FIS3,所花的时间和精力会比Webpack多一些。大部分人宁愿使用火热的技术,解决问题即可,不想花精力去研究一个看似比较复杂和有风险的工具。

十、结语

不管怎么说,webpack的确很优秀。但个人认为,在真正的前端工程化中,fis3在理念和设计上都的确领先了webpack一筹。

作为一个比较完美主义的前端老菜鸟,如今只遇到过两个完全喜欢的前端框架或者工具,一个是Vue.js,还有一个便是FIS3。

  • 二哲|风变科技

    大概可能也许,是百度?

    #1
  • bigdata

    后来者,难以被普及..

    #2
  • season.chen

    前面文风有点安利,容易惹人反感,重新修改了下……

    #3
  • ahailoveinin

    因为“百度”!

    技术没有对与错,但是“百度”的可信度不高,相应的对百度内出的产品也不太敢用。

    毕竟如果重度使用了,以后遇到“没人品”的情况,坑就大了!

    注意,不信任的是“百度”,而不是“百度家的程序员”。

    #4
  • 牙膏Tian

    我有在用,不过可能是太少用的原因吧,有问题的时候,除了自己一头扎进去,没得找到解决的办法,没得请教,所以卡在一个地方之后,就不动了,只能默默的去看了下gulp,虽然看过之后还是觉得fis好···

    #5
  • myqianlan

    生态圈

    #6
  • norion

    虽然看起好用 但是社区没起来

    #7
  • hasbug

    同样认为因为百度

    #8
  • languid

    不敢用百度

    #9
  • 第二梦哒哒哒

    问楼主一个问题 ,我用fis3构建的时候,会把页面中引用js的标签 ,搞到页面的底部 ,楼主有么有遇见 我不想开启这个功能怎么配置.

    #10
  • YUEYAO

    @第二梦哒哒哒 fis将script标签自动构建到底部是应用优化原则, 这个我觉得挺好的啊。

    #11
  • season.chen

    @第二梦哒哒哒 这都是可以通过插件配置的啊,fis在编译时会生成一个资源列表,你可以通过资源列表自定义产出规则。。fis3-postpackager-loader就是一个加载资源的插件,也可以自己定义位置。。(不过我还是习惯js放在页面底部)

    #12
  • 牙膏Tian

    不知道为什么,fis.match("**.html",{requires:["lib/common.js"]}); 用这种方式 添加html文件默认需要加载的文件没效果,这里想问几个问题?requires 数组里面的文件路径如果是错误的,是不是就不会添加?不过目前路径是对的 同样也没有添加进去

    #13
  • season.chen

    各位,有些问题还是最好去github上的fis3提问额,我只是使用的比较多,好多问题我也不清楚~~

    @牙膏Tian 我这个属性我没用过,就这点信息我暂时看不出是为什么,建议去github开个issue

    #14
  • 牙膏Tian

    @season.chen 明白,多谢

    #15
  • ousiri

    FIS的理念是很好的, 用起来也很方便, 但是自定义配置会很痛苦.

    rollup的篇幅极少, 但却在标题里面并列...

    #16
  • ShinePaul

    看百度的站长工具,做SEO bug一堆,问了还没人管,就这对待客服的态度,我就不用

    #17
  • sunaiwen

    看了楼主文章,觉得是时候看看fis3了

    #18
  • harole

    fis推广缓慢的原因: 前期是以支持百度各业务的构建工具,在开发者的潜意识里,并不是针对广大开发者的。可以考虑换一个名称!

    #19
  • 杭州的大志

    相对于fis, echarts就做的相对成功的多。

    #20
  • 牙膏Tian

    楼主是否可以建立一个fis的群,在QQ群查找上面找到了一个fis官方群,不过那些可能都是大神,比较小白的问题,确实没人鸟。

    #21
  • niphor

    fis是比较好,但是官方的大神都不鸟人的,以前提个问题,回一句这个不是让你这么用的就完了...
    风险太大
    webpack乱的一逼

    #22
  • sandman

    就是文档看的晕,有时出了问题,除了去github上去请教,没人回的话,只能干等了,然后就是手动解决出现的问题。不管百度如何不好,只希望fis是个良性的东西。

    #23
微博登录后即可参与讨论。