[译]使用webpack构建发布就绪(production-ready)的实时的SaaS

原文: A production-ready realtime SaaS with webpack
作者: Matt Krick
翻译: 黄祺(pinqy520)

译者说:

最近在看meatier,某个meteor的替代方案,解决了meteor的几个问题:技术方案陈旧(毕竟三年了)、编译扩展不方便、学习成本高等等。它直接采用现在流行技术,并且使用webpack进行打包,降低了编译扩展这块的学习成本。

虽然这篇文章是去年年底的,但是关于webpack配置这块依然值得学习(也可能会很快过时吧,哈哈)。

最后,第一次进行翻译,如有错误欢迎指出。


我是Meteor的忠实粉丝,它让一切都socket化(socketize)[1],编译(transpile)样式,然后为server生成一些代码。然而有时候,可能需要更灵活一点。

像标准的SaaS[2],我需要一个可以从CDN获取的小跳转页,一个提供实时websocket连接的门户首页,很容易在横向和纵向进行扩展

最终结果就是Meatier[3],我将其放到github上了:http://github.com/mattkrick/meatier,他解决了很多问题:

  1. JWT(JSON Web Tokens)[4]
  2. sockets
  3. 使用redux做client-side缓存
  4. 将socket状态存于redux的state中
  5. 优化和实时数据库更新

但是在本文重心在于webpack,因为有100篇webpack 101指导,却并没有一篇webpack 201(讲的都不深入?)。

在本项目中,使用了webpack 2 Beta,虽然还有一些bug,但是并不常见

构建production的webpack设置

我假设你已经设置好了一个开发用的webpack配置,如果你不知道如何创建一个开发配置......

嘿嘿嘿

使用路由来拆分你的页面

第一步先处理路由,然后让你的同步组件异步处理。参考这个例子

export default store => {
  return {
    onEnter: requireNoAuth(store),
    path: 'signup',
    getComponent: async (location, cb) => {
      let component = await System.import('./Signup');
      cb(null, component)
    }
  }
}

这里的路由并不是指的jsx,而是一个在redux store中的函数(注:类似于一个reducer专门用来处理路由),用来更好的对ruduer进行代码拆分(更多信息在下篇博客当中),重点在于System.import中,它将创建一个promise来传输这个模块,那么在webpack 2中,它将会变成一个独立的可以被动态加载的模块[5]

为client编写production-ready的webpack配置

好,现在webpack知道如何最好的来分割你的代码了,这能节约流量。然而,有时候可能不会这么完美。比如说:在某种情况下,节约额外的5kb流量,并不比增加一次额外的http请求收益更高。所以我们要使用AggressiveMergingPlugin,它能平衡你的请求大小比例。还有MinChunkSizePlugin插件,可以用来设置阈值(例如50000)来限制一些小的chunks(注:代码块,可以理解为模块)。

接下来是优化用户第二次访问的体验(如果用户会第二次访问你的网站?),我们需要尽可能多的利用用户的浏览器缓存,只传输少量的流量(意味着更少的钱和更快的速度)。我们首先拆分vendor包,因为有可能你不想BUG产生的次数和你更新React的次数一样多。

然后是进行地址更新,当客户端读取一个资源的时候,在、浏览器看到一个文件名(URL地址),如果它能够解析到一个本地缓存,那浏览器就不会发送请求。也就是说:假设你的文件叫app.js,你更新了这个文件之后,浏览器并不会重新下载它,除非你关闭了缓存。为了解决这个问题,当文件改变时,将会给每个文件分配一个hash值(放到文件名里)。

output: {
  filename: '[name]_[chunkhash].js',
  chunkFilename: '[name]_[chunkhash].js',
  ...
},

现在,我们不能在HTML中静态的请求一个app.js(为了解决上面那个问题),我们需要请求一个在每次build之后文件名都会变化的文件,可以用AssetsPlugin来创建一个查询表,来存储资源和hash后的名称。

new AssetsPlugin({
  path: path.join(root, 'build'), 
  filename: 'assets.json'
}),

然后,在生成HTML之前,我们就需要assets.json,给src赋值assets.app.jsassetsrequire('assets.json')assets.app.js是hash后的app.js的地址,同理assets.vendor.jsassets.manifest.js也一样)。

https://github.com/mattkrick/meatier/blob/master/src/server/Html.js

现在问题来了,如果一个chunk改变了,那也会改变其他chunk的hash值(因为webpack按照文件名进行获取不同的模块,那么一个文件的hash值变了,其他文件因为引用的模块路径有变化,也会变化),导致你其他的chunk也会刷新(被浏览器重新下载)。为了避免这种问题,NamedModulesPlugin能够将webpack模块编号,替换成一个实际的路径。只需要在没有参数的情况下调用它,在下一次编译中,所有的chunk都会由区分开的hash值。一般情况下,你不会想要客户端知道你的实际路径,这就是为啥我们要使用HashedModuleIdsPlugin,它会增加一些额外的流量,但是能让人安心。

现在,唯一会被刷新的chunk只有你改变过代码的那个,并且会带有webpack runtime(webpack自带的一些代码)。但是,这个webpack runtime已经被打包进了上一个公共的chunk中,可能叫vendor.js。围绕这一点,我们需要抽取出webpack runtime,因此相比于要刷新两个文件,可以只更新一个你想要的文件。

new webpack.optimize.CommonsChunkPlugin({
  names: ['vendor', 'manifest'],
  minChunks: Infinity
}),

因为现在最新的公共chunk一直都是有webpack runtime的,它将会在CommonsChunkPlugin中被提取出来。需要注意的是,它并没有相应的入口chunk,并且minChunks被设置成无限的,所以没有东西会被加进去。

为了得到HTML的内容,我们需要创建另外一个HTTP请求,但是额外的请求会造成额外的浪费,所以,我们将其嵌入其中。我们将从assets.json中获取路径,然后将其中的内容读取成字符串(打印到HTML模板)。

https://github.com/mattkrick/meatier/blob/master/src/server/createSSR.js

const assets = require('../../build/assets.json');
const readFile = promisify(fs.readFile);
assets.manifest.text = await readFile(path.join(root, 'build', path.basename(assets.manifest.js)), 'utf-8');

为server编写production-ready的webpack配置

最后一步比较麻烦,对于CSS文件你有四种选择:

  1. 将css提取成样式表(<link />),
  2. 将你组建中的样式写成inline-style的
  3. 将css提取到一系列style标签中
  4. 混合方案

我个人比较喜欢提取成样式表,以消耗额外的HTTP请求为代价(但是它加载的很快,不需要javascript,并且可以对样式进行浏览器缓存)。最理想的情况下,每个stylesheet就是一个chunk。但是现在在服务端渲染中还不可能(作者说:如果他错了就告诉他),同样的css可能会被重复多次,将会被压缩到很小,所以就让我们优化一个大的样式表吧。

现在有很多hacky的解决方案去解决这个:node并不知道如何需要require一个CSS文件的问题。如下几种:

  • 在server端忽略CSS require
  • 在每个组件中加入process.env.BROWSER环境变量
  • 将他们探测出来,放置在webpack的stats.json文件中,然后在服务端将它们插入进去。
  • 给每个组件一个能被父级访问的this.styles,然后一路被父级访问,直到创建了一个style标签。
  • 将styles放到组建的上下文中

然而,我并不喜欢上面任何一个。重写组件只为了能够使用服务端渲染,是一个错误的想法。因此,我决定在server端生成一个路由的webpack build,因为webpack知道如何处理css文件(node不知道)。它(webpack)能编译整个路由,并且用它在server端渲染每个页面。这很容易,将你的target设置为node,并且output.libraryTarget: "commonjs2"

https://github.com/mattkrick/meatier/blob/master/webpack/webpack.config.server.js

接下来用ExtractTextPlugin创建你的css文件。然而在webpack 1 和 2 中,出现了一些问题:为了将所有样式打包成一个文件,我设置allChunkstrue,结果还是生成了多个CSS文件。为了解决这个问题,我使用了LimitChunkCountPlugin,将chunk限制设置为1。结果就是,你的网站会使用一个可以被server用来渲染HTML的预处理好的路由,和一个包含网站所有样式的CSS文件。

为了加快服务端启动的时间,假设你在服务端使用了babel。你可以用如下选项排除调这个预渲染好的bundle

require('babel-register')({
  only(filename) {
    return (filename.indexOf('build') === -1 && filename.indexOf('node_modules') === -1);
  }
});

结语

好了,现在你知道如何来便写一个production ready的webpack配置文件了,它将在服务端渲染中工作,并不需要在客户端中允许javascript。你不需要重写你的组件来处理样式,并且,它在开发中依然很快。无论何时,你在开发server端的时候,你都可以运行你的production构建(build),并且你不需要在每次重启后,重新编译你的client端(bundle)。


  1. 译者注: 『一切编程都是Socket』Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现。所以使用javascript同构变成的方式,把网络请求都变成类似与读写的操作,通过websocket将前后端数据同步。

  2. 译者注:可以理解成一个web app

  3. 译者注:作者写的一个类似于meteor的webpack构建saas的模板。

  4. 译者注:https://jwt.io/

  5. 译者注:在webpack2中,遵循ES6的代码拆分方式,可以使用System.import方法,在运行时动态加载es6模块。webpack把System.import作为拆分点,然后把请求的模块放入一个单独的『块』(chunk)中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • webpack 介绍 webpack 是什么 为什么引入新的打包工具 webpack 核心思想 webpack 安...
    yxsGert阅读 6,460评论 2 71
  • 构建工具逐渐成为前端工程必备的工具,Grunt、Gulp、Fis、Webpack等等,译者有幸使用过Fis、Gul...
    陈坚生阅读 5,997评论 4 64
  • 在现在的前端开发中,前后端分离、模块化开发、版本控制、文件合并与压缩、mock数据等等一些原本后端的思想开始...
    Charlot阅读 5,433评论 1 32
  • react+redux+webpack+babel+npm+shell+git这方面的内容我会随时更新,更新内容放...
    liangklfang阅读 649评论 0 1
  • 她一直都是那个她,跟所有人一样那么的平凡。 出生于平凡的家庭,家庭完整。家里不贫苦也不富裕,条件足够她顺顺利利的上...
    非梦非语阅读 196评论 0 0