基于Redux架构的单页应用开发总结(一)

写在前面

“大学四年,细细回味。大一,面带稚嫩的面庞,一腔傻傻的热情。可爱帅气的小涵妹,带我认识时尚,好基友终生难忘。大二,踏上程序员之旅,曦点无缘,Smart不弃,恩师点拨学长提携,滴水之恩涌泉报。大三,有了自己的团队,乐雁老朱,编程游戏我们都在一起。项目经验,点点积累,低下小中探寻的是学以致用的真理。大四,杭州漂泊的一年,八爱到贝贝,小公司磨练,大公司学习,前端工程师之路,勇往直行!
振哥、超哥,带我实践和探索,彰显、健芬,让我开眼和提升自己。你们是我的良师益友,感谢每一段的指点。现在,新的团队,可靠的后背,我们不畏惧任何艰辛,未来如何,乐意笑迎!”

好久没更新博客了,因为这段时间一直被项目进度和毕业的事情压的透不过气,公司学校之间来回奔波了许久。好在,顺利完成了毕业任务,公司的项目最后也按时交付,总算可以缓下来写写文章了。接下来我会分几篇把近期开发的基于Redux的单页应用来一次技术剖析。

系统架构介绍

本项目开发基于 React + Redux + React-Route 框架,利用 webpack 进行模块化构建,前端编写语言是 JavaScript ES6,利用 babel进行转换。

|--- project
        |--- build                    // 项目打包编译目录
        |--- src                      // 项目开发的源代码
            |--- actions              // redux的动作
            |--- components           // redux的组件
            |--- containers           // redux的容器  
            |--- images               // 静态图片
            |--- mixins               // 通用的函数库
            |--- reducers             // redux的store操作
            |--- configureStore.js    // redux的store映射
            |--- index.js             // 页面入口
            |--- routes.js            // 路由配置
        |--- index.html               // 入口文件
        |--- .babelrc                 // babel配置
        |--- main.js                  // webkit打包的壳子
        |--- package.json             // 包信息
        |--- webpack.config.js        // webpack配置文件
        |--- readme.md           
"dependencies": {
    "babel-polyfill": "^6.7.4",
    "base-64": "^0.1.0",
    "immutable": "^3.7.6",
    "isomorphic-fetch": "^2.2.1",
    "moment": "^2.13.0",
    "normalizr": "^2.0.1",
    "react": "^0.14.8",
    "react-datetimepicker": "^2.0.0",
    "react-dom": "^0.14.8",
    "react-redux": "^4.4.1",
    "react-redux-spinner": "^0.4.0",
    "react-router": "^2.0.1",
    "react-router-redux": "^4.0.1",
    "redux": "^3.3.1",
    "redux-immutablejs": "0.0.8",
    "redux-logger": "^2.6.1",
    "redux-thunk": "^2.0.1"
  },
  "devDependencies": {
    "babel-core": "^6.7.5",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.6.0",
    "babel-preset-react": "^6.5.0",
    "babel-preset-stage-1": "^6.5.0",
    "css-loader": "^0.23.1",
    "file-loader": "^0.8.5",
    "img-loader": "^1.2.2",
    "less": "^2.6.1",
    "less-loader": "^2.2.3",
    "mocha": "^2.4.5",
    "style-loader": "^0.13.1",
    "url-loader": "^0.5.7",
    "webpack": "^1.12.14"
  }

webpack配置

也算是实际体验了一把webpack,不得不说,论React最佳搭档,非此货莫属!真的很强大,很好用。

var webpack = require('webpack');   // 引入webpack模块
var path = require('path');         // 引入node的path模块
var nodeModulesPath = path.join(__dirname, '/node_modules');  // 设置node_modules目录

module.exports = {
    // 配置入口(此处定义了双入口)
    entry: {
        bundle: './src/index',
        vendor: ['react', 'react-dom', 'redux']
    },
    // 配置输出目录
    output: {
        path: path.join(__dirname, '/build'),
        publicPath: "/assets/",
        filename: 'bundle.js'
    },
    module: {
        noParse: [
            path.join(nodeModulesPath, '/react/dist/react.min'),
            path.join(nodeModulesPath, '/react-dom/dist/react-dom.min'),
            path.join(nodeModulesPath, '/redux/dist/redux.min'),
        ],
        // 加载器
        loaders: [
            // less加载器
            { test: /\.less$/, loader: 'style!css!less' },
            // babel加载器
            { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' },
            // 图片加载器(图片超过8k会自动转base64格式)
            { test: /\.(gif|jpg|png)$/, loader: "url?limit=8192&name=images/[name].[hash].[ext]"},
            // 加载icon字体文件
            { test: /\.(woff|svg|eot|ttf)$/, loader: 'url?limit=50000&name=fonts/[name].[hash].[ext]'}
        ]
    },
    // 外部依赖(不会打包到bundle.js里)
    externals: { 
        'citys': 'Citys'
    },
    // 插件
    plugins: [
        //new webpack.HotModuleReplacementPlugin(),  // 版本上线时开启
        new webpack.DefinePlugin({
            // 定义生产环境
            "process.env": {
                NODE_ENV: JSON.stringify("production")
            }
        }),
        //new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), // 版本上线时开启
        // 公共部分会被抽离到vendor.js里
        new webpack.optimize.CommonsChunkPlugin('vendor',  'vendor.js'),
        // 比对id的使用频率和分布来得出最短的id分配给使用频率高的模块
        new webpack.optimize.OccurenceOrderPlugin(),
        // 允许错误不打断程序
        new webpack.NoErrorsPlugin()
    ],
};

延伸-Webpack性能优化

最小化

为了瘦身你的js(还有你的css,如果你用到css-loader的话)webpack支持一个简单的配置项:

new webpack.optimize.UglifyJsPlugin()

这是一种简单而有效的方法来优化你的webapp。而webpack还提供了modules 和 chunks ids 来区分他们俩。利用下面的配置项,webpack就能够比对id的使用频率和分布来得出最短的id分配给使用频率高的模块。

new webpack.optimize.OccurenceOrderPlugin()

入口文件对于文件大小有较高的优先级(入口文件压缩优化率尽量的好)

去重

如果你使用了一些有着很酷的依赖树的库,那么它可能存在一些文件是重复的。webpack可以找到这些文件并去重。这保证了重复的代码不被大包到bundle文件里面去,取而代之的是运行时请求一个封装的函数。不会影响语义

new webpack.optimize.DedupePlugin()

这个功能可能会增加入口模块的一些花销

对于chunks的优化

当coding的时候,你可能已经添加了许多分割点来按需加载。但编译完了之后你发现有太多细小的模块造成了很大的HTTP损耗。幸运的是Webpack可以处理这个问题,你可以做下面两件事情来合并一些请求:

  • Limit the maximum chunk count with
new webpack.optimize.LimitChunkCountPlugin({maxChunks: 15})
  • Limit the minimum chunk size with
new webpack.optimize.MinChunkSizePlugin({minChunkSize: 10000})

Webpack通过合并来管理这些异步加载的模块(合并更多的时候发生在当前这个chunk有复用的地方)。文件只要在入口页面加载的时候没有被引入,那么就不会被合并到chunk里面去。

单页

Webpack 是为单页应用量身定做的 你可以把app拆成很多chunk,这些chunk由路由来加载。入口模块仅仅包含路由和一些库,没有别的内容。这么做在用户通过导航浏览表现很好,但是初始化页面加载的时候你需要2个网络请求:一个是请求路由,一个是加载当前内容。

如果你利用HTML5的HistoryAPI 来让URL影响当前内容页的话。你的服务器可以知道那个内容页面将被客户端请求。为了节约请求数,服务端可以把要请求的内容模块放到响应头里面:以script标签的形式来添加,浏览器将并行的加载这俩请求。

<script src="entry-chunk.js" type="text/javascript" charset="utf-8"></script>
<script src="3.chunk.js" type="text/javascript" charset="utf-8"></script>

你可以从build stas里面提取出chunk的filename (stats-webpack-plugin )

多页

当编译一个多页面的app时,你想要在页面之间共享一些代码。这在webpack看来很简单的:只需要和多个入口文件一起编译就好

webpack p1=./page1 p2=./page2 p3=./page3 [name].entry-chunk.js
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        p3: "./page3"
    },
    output: {
        filename: "[name].entry.chunk.js"
    }
}

由上面可以产出多个入口文件

p1.entry.chunk.js, p2.entry.chunk.js and p3.entry.chunk.js

但是可以增加一个chunk来共享她们中的一些代码。 如果你的chunks有一些公用的modules,那我推荐一个很酷的插件CommonsChunkPlugin,它能辨别共用模块并把他们放倒一个文件里面去。你需要在你的页面里添加两个script标签来分别引入入口文件和共用模块文件。

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        p3: "./page3"
    },
    output: {
        filename: "[name].entry.chunk.js"
    },
    plugins: [
        new CommonsChunkPlugin("commons.chunk.js")
    ]
}

由上面可以产出入口文件

p1.entry.chunk.js, p2.entry.chunk.js and p3.entry.chunk.js

和共用文件

commons.chunk.js

在页面中要首先加载 commons.chunk.js 在加载xx.entry.chunk.js 你可以出实话很多个commons chunks ,通过选择不同的入口文件。并且你可以堆叠使用这些commons chunks。

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        p3: "./page3",
        ap1: "./admin/page1",
        ap2: "./admin/page2"
    },
    output: {
        filename: "[name].js"
    },
    plugins: [
        new CommonsChunkPlugin("admin-commons.js", ["ap1", "ap2"]),
        new CommonsChunkPlugin("commons.js", ["p1", "p2", "admin-commons.js"])
    ]
};

输出结果:

page1.html: commons.js, p1.js
page2.html: commons.js, p2.js
page3.html: p3.js
admin-page1.html: commons.js, admin-commons.js, ap1.js
admin-page2.html: commons.js, admin-commons.js, ap2.js

另外你可以将多个共用文件打包到一个共用文件中。

var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
module.exports = {
    entry: {
        p1: "./page1",
        p2: "./page2",
        commons: "./entry-for-the-commons-chunk"
    },
    plugins: [
        new CommonsChunkPlugin("commons", "commons.js")
    ]
};

@参考 赵飞 《webpack 性能优化》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值