2019独角兽企业重金招聘Python工程师标准>>>
重构起点
在一个老项目中用webpack对前端代码进行重构,重构的重心在于JS部分。这个老项目呢,有2年以上的历史了。
JS部分从底数起:
- Mootools - core/more 1.5.0
- jQuery - 1.11.1
- underscore,我也不记得什么版本了
- uikit + uikit部分components
- 用户开发的底层JS:mootools-extends扩展,uikit-extends - 基于uikit写的一套ui部分交互的东西,mobile-detect - 适配个版本安卓用的东西,custom-functions - 各种常用方法。
- jQuery插件:jquery-address、jquery-mobile-event、owl-carousel、tablesaw、jquery.transit、jquery.event.drop等
- requirejs
- 各种开源的工具:ckeditor、jsoneditor、plupload、DateTimePicker(Mootools版)、highcharts等
- 用户开发的上层JS:ui - 更具体的ui控制代码,shopping - 与购物车、交易相关的代码
上述这些是这次重构中,明确在前台能够找得到、有明确使用的类库,其实很多都是擦边球了,但怎么说还是有用到。
重构基础JS并合并打包
这次重构有一些目的,就是起码开源部分的类库,能更新的,应该尽可能的更新,能不用的类库,尽量去掉。
- Mootools 更新 1.6+,去掉More库
- jQuery,更新2.2+
- underscore换成lodash,underscore作者已经哭晕了,coffee渐渐es6取代,underscore被lodash取代,backbones,也不是非他不可,前端mvc如phpmvc一般雨后春笋般冒出,underscore也很久不更新了。
- uikit 更新到2.15+
- requirejs,Good bye!!
- 清查无用的jQuery插件
- 开源的工具也依次更新等。
- 增加React(react、react-dom)
其次,从内到外,重新合并打包出基础js(页面只加载一次这个js)。
Mootools(这家伙重写原生对象) => jQuery => lodash => uikit => 用户开发的底层JS => jQuery插件 => 用户开发的上层JS => 本次重构新写的JS
React和ReactDOM实际上实在本次重构新写的js中引入的。
上述内容是需要重新打包的js内容。webpack的入口文件——entry代码如下:
// 第一阶段
var jQuery = require('./lib/jquery_hack');
var lodash = require('./lib/lodash');
// var Mootools = require('./lib/mootools-core-1.5.1-full-nocompat');
window.$ = document.id;
window.jQuery = jQuery;
window._ = lodash;
// 第二阶段 uikit ui部分扩展
var uikit = require('./lib/uikit');
// 第三阶段 各种乱七八糟的类库,jQuery扩展之类的
require('./lib/jquery-address');
require('./lib/jquery-mobile-events');
require('./lib/jquery-datetimepicker');
require('./lib/jquery.pinto');
require('./lib/spark-md5');
// 第四阶段 自己写的js加载
require('./base/mootools-extends');
require('./base/mobile-detect');
require('./base/custom-functions');
var uk = require('./base/uikit-extends');
// 这是一个jquery的插件
require('./base/tablesaw');
var ui = require('./main/ui');
var shopping = require('./main/shopping');
require('./components');
jQuery(document).ready(function() {
ui.initNumberInput();
require('./page');
});
jQuery.fn.extend({
animateCss: function (animationName) {
var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';
jQuery(this).addClass('animated ' + animationName).one(animationEnd, function() {
jQuery(this).removeClass('animated ' + animationName);
});
}
});
经过多方尝试,发现Mootools新版Core库,在PC端使用没有问题,但是在手机中,存在各种错误,尤其是在手机中的webkit系浏览器中,估计是因为更新了es6的scoping的问题,因为mootools的源码很多在块({})与块之间引用变量的问题,比如Slick、Element等,在这个块定义的方法,在下一个块就调用不到了。而且苹果和安卓,还各有不同的特征,修正了安卓下的问题,苹果又出毛病了。所以,最后实在没办法,Mootools 1.6.+ Core的代码单独在页面优先加载,之后是按照上述规则打包出来的基础JS。
下面是webpack的webpack.config.js,这个文件是用于生成开发版本调试用的:
const path = require("path");
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const gbdo = 'public/assets', test = 'assets/public';
const basePath = gbdo;
module.exports = {
entry: {
gbdo: ['./assets/js/app.js'],
},
// target: 'node', // http://stackoverflow.com/questions/33001237/webpack-not-excluding-node-modules
// externals: [nodeExternals()],
output: {
path: path.resolve(__dirname, basePath),
// publicPath: "/",
filename: '[name].js'
},
module: {
loaders: [
{
test: /\.less$/,
loader: ExtractTextPlugin.extract('style', 'css!less')
},
{
test: /\.styl$/,
loader: ExtractTextPlugin.extract('style', 'css-loader!stylus-loader')
// loader: 'css-loader!stylus-loader?paths=node_modules/bootstrap-stylus/stylus/'
},
{
test: /\.css$/,
loader: "style!css"
},
{
test: /\.(png|jpg|jpeg|gif|(woff|woff2)?(\?v=[0-9]\.[0-9]\.[0-9])?)$/,
loader: 'url-loader?limit=1000'
},
{
test: /\.(ttf|eot|svg)(\?[\s\S]+)?$/,
loader: 'file'
},
{
test: /\.(es6|jsx)?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['es2015', 'react'],
plugins: [
"transform-es2015-block-scoping",
"transform-class-properties",
"transform-es2015-computed-properties"
]
}
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
output: {
comments: false,
},
}),
]
};
下面是webpack.build.js,是用于打包出最终的版本,以push到git用的:
const path = require("path");
const webpack = require('webpack');
var config = require('./webpack.config');
config.plugins.push(new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify('production')
}
}));
module.exports = config;
为什么单独做一个webpack.build呢?这个后面会说到,其实主要问题是因为react这个多事鬼。
而ckeditor、jsoneditor、plupload、datetimepicker(jQuery版)、highcharts,这些属于外围的工具辅助类,以页面需要用到的时候,才发起调用。
webpack vs Gulp
其实我还有另外一个一模一样的打包的gulpfile.js,内容就不发了。两者打包出来的容量是相当的,webpack是696kb,gulp是691kb,不过就调试环境而言,差别就很大了,如果webpack以dev-server方式启动,他会保留完整的代码引用结构,以方便你检阅各个部分的代码:
所以如果同样是合并输出,webpack提供了更多的细节以让你进行调试。
当然,其实gulp也可以透过sourceMap的方式来实现,不过我不得不吐槽,gulp如果要生成js的sourceMap,执行时间相差实在太大了,稍微大一些的文件,或者是结合了babel,起码要等上几秒乃至更长的时间,完全没有实用的价值。
而且就合并处理的方式而言,当你执行webpack或者webpack-dev-server,如果转译的js或者css有错,他只会跳过这个文件,而继续往后执行,并且不中断当前的进程,也即你并不需要去手动重启webpack。
而gulp的话,就比较头疼了,虽然加入gutils对于部分的错误可以跳过,但是还是有一些异常是会让gulp中断的,所以用gulp watch的时候,你修改代码,必须小心翼翼,起码必须保证代码不能包含一些语法检查中可能引发的异常状态。
而关于i/o检索的准确性,gulp一直让我比较头疼,比如假定我监控的是指定的目录下的全部js文件,如果你新加了一个js文件,gulp watch是不会马上发现的,虽然他迟早会发现这个文件存在,但这需要有一个周期,所以更好的办法是停止当前的gulp watch,然后重新启动。
其次是,gulp虽然可以指定一个目录下的文件来检索,比如:js/*.js,js/*/*.js,可是一个很重要的问题是,这些文件加载是有序的,所以实际上你还是要一条一条来写,或者使用gulp-order、gulp-sort之类的插件,但实际上你还是必须手动指定具体的内容的。非常不爽。
相较而言,webpack则完全保留了node_modules的目录引用机制,就是该目录下的index.js索引文件,比如:require('./dir'),实际上就是引用的是dir/index.js。
entry入口文件,你确定引用需要的目录的索引文件,然后启动webpack或者webpack-dev-server,之后增加或者删除文件,修改entry文件,或者修改目录索引文件,webpack都会实时检索到更新,这比gulp操作起来要简便多了。
最后就是代码规模的问题,上面说了,同样的素材源代码,同样的uglifyJS配置,最基本的压缩,如果不压缩js,gulp还是挺快的,可是加上uglifyJS,gulp就成了一台年迈的老爷车。而webpack,除了上述的源代码,还增加了很多很多webpack自己的源代码,最后合并、并进行uglifyJS compress,依旧轻松愉快。
详细的技术细节我就不分析太多了,webpack的hot update,得益于他清晰结构化的合并代码结构,而不是纯粹简单的合并,不过这也会让不熟悉js的用户——尤其是不熟悉浏览器环境下调试的用户,增加了一层复杂度。但从实际使用的体验层面来说,webpack比gulp带来更好的体验感——最关键的是,gulp,还没用,就要装一大堆插件:
258个文件,你确定不是在逗我?我只是想写js……
webpack则相对要集中得多了。
从模式上来说,webpack关注的是集中,整合,并且严谨的检查前端引用的每一个资源,并根据你的配置打包输出到指定的目录下,一丝不苟,但容错性又很强。
相对来说,gulp兼容性很强,容忍,松散,效能低下,更严格意义上说,他只是你系统文件流的搬运工,而且容错性很低。
从上手来说,gulp和webpack初始上手都很容易,大家表层的配置和调用的接口都设计的巨简单。但是两者深入使用的时候,都很复杂,gulp你需要安装比jquery更多的插件,可能你项目实际用到的插件库,还没有gulp多。而webpack的配置,隐晦的东西太多太多,他是可以很牛掰,而且很多东西其实官网也有提过(但也就是那么一提),你不翻阅github issues,stackoverflow,根本不了解,哦原来官网那么简单一提的东西,原来是有那么深刻的用意的。
所以严格来说,webpack要熟练精通的话,可能会需要更高的投入。