写在前面
“大学四年,细细回味。大一,面带稚嫩的面庞,一腔傻傻的热情。可爱帅气的小涵妹,带我认识时尚,好基友终生难忘。大二,踏上程序员之旅,曦点无缘,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 性能优化》