Eisen's Blog

© 2024. All rights reserved.

Learn wercker

2016 June-02

半年来一直在做一个 PaaS 的项目,比较关注市面上的相关产品。最近发现一个叫做 wercker 的项目,感觉做的还不错,介绍一下。

wercker 要解决的问题

目前有很多的平台(如 mesos, rancher, kubernetes)都支持以 docker image 的形式进行应用的部署,但是却没有很多的工具帮助将 ci/cd 与这些平台进行更好的对接。而 wercker 的口号是 From code to container,强调自己可以做 ci/cd 的事情将代码转化为容器。那么之后就可以将这个容器作为交付的内容在需要的环境进行部署了。

wercker 的特性

  1. pipeline as cde

    wercker 提供一个类似于 ansiblewercker.yml 并提供与 ansible 类似的自定义命令来做部署的工作。

    自定义命令的功能非常的强大,种类也非常的丰富。例如 npm-install 安装 node 的依赖,internal/docker-push 将生成的 image 上传到 docker registrymarathon-deploy 将应用部署到 marathon 平台。

    整个 pipeline 可以通过这些命令拼装起来,所有的 pipeline 都可以通过一个 wercker.yml 文件进行管理。

  2. 本地环境

    wercker 有一个命令行工具 wercker-cli 支持在本地通过 dockerwercker-cli 构建一个本地的开发环境,并且支持在本地环境提供 backing service

  3. 多 vendor 支持

    wercker 可以和多个 PaaS 对接的,包括 heroku kubernetes marathon ecs 等。这一点非常的难能可贵,想象一下,作为一个开发者,当有了类似于 ecs 或者 heroku 这样的公有云之后再配合 wercker 这样的工具可以快速的搭建 pipeline 以及完成以前需要花费更多时间才能得到的 ci/cd 开发效率真是大大的提升。

  4. ui 界面

    提供一个 ui 界面管理整个 pipeline

  5. 与 github & bitbucket 对接

    支持 github bitbucket hook,在有新的 commit 之后自动构建、部署。 管理关键数据,有些数据不适合存放在 wercker.yml 中,例如 herokuaccesskeydocker-hub 的账号密码。

参考

wercker


Webpack and Redux, minify the output bundle

2016 May-29

webpack + redux + react 开发前端最终会将所有的 js 依赖打包成为一个(或者几个,因配置不同而不同)js 文件。虽然 webpack 很好的帮助我们解决了依赖的问题,避免了一大堆分散的 js 文件出现在页面里,但是最终打包出来的 js 文件依然会变成所有依赖的 js 的 size 的总和,成为前端页面响应速度的巨大负担。不过通过一些调优可以最大化的减少最终的打包文件的大小并提升运行性能。

dev & production env

首先,我们可以通过设定不同的 NODE_ENV 环境变量去控制在不同的环境下引入的配置。通过在 webpack.config.js 中读取 process.env.NODE_ENV 可以为 webpack 提供不同的 plugin 用于控制 webpack 的打包机制。下面是一个例子:

var plugins = process.env.NODE_ENV === 'production' ? [
  new webpack.DefinePlugin({
    API_PREFIX: JSON.stringify(process.env.API_PREFIX) || '"{{API_PREFIX}}"',
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
  }),
  new webpack.optimize.DedupePlugin(),
  new webpack.optimize.OccurenceOrderPlugin(),
  new webpack.optimize.UglifyJsPlugin({
    compress: {
      warnings: false
    }
  })
] : [
  new webpack.DefinePlugin({
    API_PREFIX: JSON.stringify(process.env.API_PREFIX) || '"{{API_PREFIX}}"',
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
  })
];

其中 DefinePlugin 可以为 webpack 提供全局变量,这里我们利用它将 node 中的 process.env.NODE_ENV 转换为 webpack 构建最终的 js 时用到的全局变量。如果直接在 webpack 构建时处理的 js 文件中直接引用 nodejs 才能读取的 process.env 是不会有任何效果的。

然后,我们通过这个 process.env.NODE_ENV 的不同加载不同的 configureStore.js,将在 production 环境下用不到的 redux 中间件清理掉。

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./configureStore.prod')
} else {
  module.exports = require('./configureStore.dev')
}

configureStore.prod.js:

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from '../reducers/index'

export default function configureStore(initialState) {
  const store = createStore(rootReducer, initialState, applyMiddleware(thunk));
  return store;
}

configureStore.dev.js:

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import createLogger from 'redux-logger';
import rootReducer from '../reducers/index'

export default function configureStore(initialState) {
  const store = createStore(rootReducer, initialState, applyMiddleware(thunk, createLogger()));
  return store;
}

利用 webpack 插件压缩输出文件

在各种前端构建工具中都受不了 uglify 的过程,webpack 也不例外。在上文 webpack.config.js 的例子中对 production 环境下的 webpack 就提供了各种插件用于压缩文件。

new webpack.optimize.UglifyJsPlugin({
  compress: {
    warnings: false
  }
})

采用 webpack-bundle-size-analyzer 分析依赖大小

如果以上两种优化都做了(尤其是 uglify)那么恭喜你,你的 js 输出文件已经是处理前的三分之一了。我在自己的一个项目中最终的 bundle.js 文件从 2.2MB 降到了 800KB 效果还是非常好的。

但是 800KB 还是好大的一个文件,如果还想继续优化呢?那就需要有针对性的进行优化了。我们在开发的过场中依赖了乱七八糟的 package 那么是不是能通过减少依赖或者是更换依赖的方式来进一步的减少最终输出文件的大小呢?那么,首先需要知道每个依赖占据的比例了。这里我们采用一个工具 webpack-bundle-size-analyzer 分析所有依赖的大小。

npm install -g webpack-bundle-size-analyzer

然后

webpack --json | webpack-bundle-size-analyzer

就可以看到所有依赖的大小占比了:

react: 667.34 KB (28.9%)
  fbjs: 33.59 KB (5.03%)
  <self>: 633.74 KB (95.0%)
moment: 454.54 KB (19.7%)
bootstrap: 273.93 KB (11.9%)
jquery: 251.51 KB (10.9%)
react-router: 159.31 KB (6.91%)
  history: 52.69 KB (33.1%)
    deep-equal: 3.8 KB (7.22%)
    query-string: 1.62 KB (3.08%)
      strict-uri-encode: 182 B (10.9%)
      <self>: 1.45 KB (89.1%)
    <self>: 47.26 KB (89.7%)
  warning: 1.76 KB (1.11%)
  invariant: 1.48 KB (0.929%)
  hoist-non-react-statics: 1.35 KB (0.850%)
  <self>: 102.03 KB (64.0%)
formsy-react-components: 36.24 KB (1.57%)
  classnames: 2.58 KB (7.11%)
  <self>: 33.66 KB (92.9%)
superagent: 30.57 KB (1.32%)
  component-emitter: 3.11 KB (10.2%)
  reduce-component: 405 B (1.29%)
  <self>: 27.06 KB (88.5%)
formsy-react: 30.55 KB (1.32%)
  form-data-to-object: 1.19 KB (3.91%)
  <self>: 29.36 KB (96.1%)
axios: 29.18 KB (1.26%)
redux: 25.8 KB (1.12%)
  lodash: 3.34 KB (12.9%)
  symbol-observable: 451 B (1.71%)
  <self>: 22.02 KB (85.4%)
react-redux: 25.54 KB (1.11%)
  lodash: 3.34 KB (13.1%)
  invariant: 1.48 KB (5.80%)
  hoist-non-react-statics: 1.35 KB (5.30%)
  <self>: 19.37 KB (75.8%)
redux-logger: 8.29 KB (0.359%)
style-loader: 6.99 KB (0.303%)
webpack: 3 KB (0.130%)
  node-libs-browser: 2.76 KB (91.8%)
    process: 2.76 KB (100%)
    <self>: 0 B (0.00%)
  <self>: 251 B (8.17%)
object-assign: 1.95 KB (0.0844%)
css-loader: 1.47 KB (0.0638%)
redux-thunk: 306 B (0.0130%)
superagent-prefix: 198 B (0.00838%)
react-dom: 63 B (0.00267%)
<self>: 300.1 KB (13.0%)

这里在优化前的输出情况,其中 react 最大,占整个输出的 28.9%,未压缩前 bundle.js2.24MBreact 是我们的核心依赖,是没什么办法做优化了,但是占比第二高的 moment 仅仅是一个用于日期格式化的工具,占比如此之高实在是可疑。通过搜索其他的可选方案,我将 moment 替换为 date-fns,bundle 文件减小为 1.7MB 压缩后文件减小为 558KB

最后的最后

如果你真的还觉得太大了...那,就只有靠 nginx 那边做 gzip 优化了...

gzip  on;
gzip_types      text/plain application/xml text/css text/html application/javascript;

参考

  1. webpack optimization
  2. Optimizing React + ES6 + Webpack Production Build
  3. date-format-without-moment
  4. passing-environment-dependent-variables-in-webpack
  5. nginx gzip

Webpack with Bootstrap

2016 May-20

最近开始利用业余时间采用 react + redux 的前端架构山寨一个金数据(或者说是 WuFoo,毕竟两个东西看起来真的很像)以增加自己对这些框架的熟练度。在这个过程中记录下一些自己遇到的坑。今天就是一个 webpack 如何和 bootstrap 结合的坑。

为了在项目之初就一个不是那么丑的界面,都会选择一些比较成熟的前端 css 框架。bootstrap 是比较流行的一个。bootstrap 一方面是基本的 css 另一方面还有一些 jQuery 的插件形式的类库支持其中的一些组件,当然还有一些它所需要的字体文件。那么这里问题就来了:

  1. 如何在 webpack 中引入 jQuery 以及它的插件
  2. 如何在 webpack 引入一些其他类型的文件,例如字体

引入 bootstrap

首先,我们还是要安装 bootstrap 以及它所依赖的 jquery

npm install --save bootstrap-sass jquery

这里顺便说一句,虽然 jquery 看似过时了,但是它所构建的生态是非常庞大的,尤其是像 jquery-ui 这样的东西可以说是一些富交互应用所必须的。那么如何将 reactcomponentjquery 的一些组件很好的结合是在选择 react 这样的框架之初就考虑进去的。后面在涉及到一些复杂的交互的时候会出现 jquery-uireact 一起使用的例子。

然后,我们可以在 webpack.config.js 中以 entry 的形式引入 bootstrap-loader

var path = require("path");

module.exports = {
  devtool: 'cheap-module-source-map',
  entry: [
    'bootstrap-loader',
    './index.js'
  ],
  output: {
    path: path.join(__dirname, "dist"),
    filename: "bundle.js"
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader"
      },
      {
        test: /\.scss$/,
        exclude: /node_modules/,
        loader: "style!css!sass"
      }
    ]
  }
};

注意看 entry 本来就是一个数组,我们可以在这里引入多个入口。

Use bootstrap-loader

做了一番调研之后发现其实没必要自己把所有的事情都做了,有这么一个 bootstrap-loader 可以帮助在 webpack 的项目中引入 bootstrap

npm install --save-dev bootstrap-loader

不过单单是安装它是不过的,其实 bootstrap-loader 所做的事情就是帮助我们把各种样子的文件引入到我们的项目中,那么为了处理不同类型的文件需要一些其他的 loader 的支持(前面的博客有提及 webpack 只能处理 js 如果需要处理其他类型的东西就需要 loader 的帮助)。这里,我们还要引入一大堆的 loader

npm install --save-dev resolve-url-loader url-loader file-loader imports-loader

其中 file-loader 用于加载其他类型的文件,url-loaderfile-loader 类似,只是在文件比较小的时候返回 Data Url 的形式。resolve-url-loader 和之前提到的 sass-loader 一起使用,用于处理 sassurl() 的路径。这些 loader 都要和 webpack.config.js 中的 loaders 配置项配合使用:

var path = require("path");

module.exports = {
  ...
  module: {
    loaders: [
      ...
      {
        test: /\.woff2?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: "url?limit=10000"
      },
      {
        test: /\.(ttf|eot|svg)(\?[\s\S]+)?$/,
        loader: 'file'
      }
    ]
  }
};

Use imports-loader to support jQuery

imports-loader 是个很有意思的 loader 它定义了一个简单的格式用于引入使用它的类库所需要的依赖。

module.exports = {
  ...
  module: {
    loaders: [
      ...
      {
        test: /bootstrap-sass\/assets\/javascripts\//,
        loader: 'imports?jQuery=jquery'
      }
    ]
  }
};

如上所示,在 webpack.config.js 中加入这样一个 loader。其中说明在引入 bootstrap 下的 javascripts 时,为他们提供 jQuery 这样的变量。那么 imports-loader 会在引入 bootstrapjs 之前为他们提供如下的代码:

  var jQuery = require('jquery');

估计在后续使用 jquery 的其他东西的时候还会用到它的。

参考

  1. bootstrap-loader
  2. imports-loader
  3. file-loader
  4. url-loader
  5. resolve-url-loader