我是14年入的程序员雄师,当时主java
兼具前端开辟的活儿,在如今看来的一些流开辟框架和新兴头脑,早在node.js
最早进入人人视野的时刻就流行起来了,只是在当时博主并没有关注前端的生态圈(然则java彷佛也并没有关注,逃),所以照样处在许多人所形貌的刀耕火种
的阶段,前端代码悉数挂载到全局作用域,包含插件导出的变量。那更别提组件化
和模块化
的编程头脑了,以至代码都没必要紧缩优化就直接上传到效劳器宣布了。
厥后换了一家公司,没有前端开辟这个职位,是从javaer转过去的,由于项目须要,逐渐的也就座实了这个岗亭。项目到如今(2014年8月-2017年7月22日)一共涌现了三个阶段
- 用着十年前的开辟(或许叫整合)手艺的大略期
- 阅历4、5个月的半模块化革新的准当代期
- 到如今能整合全局资本(仅限web静态资本),随便整合新手艺的当代期(未实行)
为何要不停的去折腾,去革新?仅仅是为了跟上“当代”的步调吗?下面我将报告每一个阶段是怎样无痛革新的,为何要革新。
从大略期
到准当代期
举个例子,我们之前的代码是如许的
html页面部份
<html>
<head>
<link href="style.css" rel="stylesheet"/>
</head>
<body>
<!-- 通用的代码 -->
<script src="common.js"></script>
<!-- 第三方的插件代码 -->
<script src="plugin.js"></script>
<!-- 我们的主进口 -->
<script src="individual.js"></script>
</body>
</html>
javascript部份
在common.js
里,是我们的定义的通用函数,比方一些特定组件的部份代码如header
或footer
,或许是字符串处置惩罚,日期格式化的函数等等,这些函数都以对象或函数的情势暴露在全局作用域里,异常的芜杂和不安全,跟着代码量的增添,轻易致使掩盖,涌现难以预料的bug,另有一个致命的缺点就是没法按需加载资本,我哪怕只是用到了个中一个小小的常量,都须要援用全部文件,然后从全局作用域里拿。
// common.js
var Header = {
var1: '',
var2: {},
fn1: function() {
// some code
},
fn2: function() {
// some code
}
}
function strReplace() {
// some code
}
...
// individual.js
// 或许我们早已有憬悟 运用了自实行匿名函数来防备全局变量的污染
(function() {
// 这里我们须要用到commonjs的函数 常量等
var afterHandleStr = strReplace(str);
// 或许我们遗忘strReplace函数已存在全局作用域又或许换了一个人
// 来保护这个文件能够又会定义一个函数叫strReplace
function strReplace() {
// 那末此时依据javascript特征,本来的函数已被掩盖了,
// 上面的挪用逻辑优先从近来的作用域最早找,因而会实行到这里
}
...
}());
由于项目是迭代开辟的,功用一点点叠加上去,考虑到全部项目的生命周期,不至于到后期完全没法保护,所以我们必需以文雅的姿势去重构全部项目的资本援用体式格局,那就是模块化,一个模块做一件事变,并暴露它对外供应的接口以供具象化的页面来运用。比方header
,footer
,nav
,sidebar
,utils
等等。前端的模块化有俩个范例,一个AMD(Asynchronous Module Definition)
,一个是CMD(Common Module Definition)
,前者是异步模块定义,推重依靠前置,后者是通用模块定义,推重依靠就近,AMD的代表框架有requirejs
,CMD的代表框架有seajs
,都是很优异的作品,这里对两者有细致的引见。末了我挑选了requirejs
作为本次重构的基础,实在就当是的代码来讲,革新起来并没有什么难度,就是须要仔细,仔细,仔细,只须要将common.js
这个通用模块举行拆分就好了,页面只须要引入一个js文件,以下面如许
<html>
<head>
<link href="style.css" rel="stylesheet"/>
</head>
<body>
...
<script type="text/javascript" data-main="/individual.js" src="/require.js"></script>
</body>
</html>
data-main
是我们的代码主进口,src
是requireJs
的源码。从文件援用来讲,最少我们没必要再体贴每次运用一个插件都要手动来到场一个script
标签了,怎样援用呢?我下面会引见。
假如我们之前的代码是如许的
// common.js
(function(){
var exportObj = {
aa: 'aa',
bb: 'bb'
...
}
var utils = {
replaceStr: function() {
}
...
}
// 放到全局作用域
window.exportObj = exportObj;
window.utils = utils;
}());
// individual。js
(function(){
var aa = constants.aa;
var bb = constants.bb;
var tempStr = utils.replaceStr('tempStr');
}());
上面的代码运用了两个全局对象,constants
和utils
,那末革新后应该是:
// constants.js
// 假如它不依靠于其他模块,就没必要声明依靠的数组
define( function() {
var exportObj = {
aa: 'aa',
bb: 'bb'
...
}
// 返回我们要暴露出来的对象,没必要再放到全局作用域
return exportObj;
} );
// utils.js
define( function() {
// 返回我们要暴露出来的对象,没必要再放到全局作用域
return {
replaceStr: function() {
}
...
};
} );
// individual
define( [
'constants',
'utils'
], function( consts, utils ) {
var aa = consts.aa;
var bb = consts.bb;
var tempStr = utils.replaceStr('tempStr');
} );
// 或许
define( [
'constants',
'utils'
], function() {
var consts = require('constants');
var utils = require('utils');
var aa = consts.aa;
var bb = consts.bb;
var tempStr = utils.replaceStr('tempStr');
} );
是不是是觉得毫无挑战性,对,这就是一个体力活,仔细点就好了
我们没必要忧郁还须要手动去修正第三方插件,如今的主流插件基础都邑
UMD
体式格局去适配,也就是兼容了AMD
,CMD
,所以只须要直接援用第三方插件就好了,没必要再去html文件里手动援用script标签了,其他详细完成细节和必备的设置能够参照requirejs官网的例子
比及革新完,也还没有兴奋的完毕,我们的准当代期增添了一个优化环节,官方供应了r.js
这个优化器来帮我们打包紧缩代码(毕竟临盆环境过量的要求数照样不被许可的),此时的革新才真的做到了模块化,能优化,从大略期无痛过渡到准当代期。此时的代码,实在已具有了进入当代期的要求,那就是范例模块化。下面是我们行将举行的革新,顺遂过渡到当代期,从而拥抱你想运用的新手艺
从准当代期
到当代期
实在这个阶段,由于对一些新东西新手艺的不熟悉,绕了许多弯子,花费了不少的精神,幸亏弄出来了,基于webpack
构建东西,解放键盘F5
,到场代码作风和范例的搜检东西,到场ECMAScript 6
语法转换东西等等,为何要运用这些,归纳综合为主要以下几点:
- 提拔开辟效力和代码质量
- 新语法新和手艺能处理开辟上的许多痛点和盲点
- 壮大的整合性和包容性(相对于关闭的
r.js
优化器终究可定制了) - emmmmmm思考中
起首我们引见一下webapck
是什么
这是webpack
官方文档首页对其的简朴形貌(ps: 实在中心的正方体是会扭转的哦),壮大的webpack
能整合一切依靠的文件举行处置惩罚,如less
编译(依靠less-loader
),ES6
语法转换(依靠babel-loader
),文件hash
增加,自动上传ftp宣布临盆环境等等。另有就是webpack-dev-server
这个开辟神器,热替代、自动监测文件变化革新浏览器,虽然当代期
并没有用到现实项目中去,然则到如今(2017年7月22日),我已能完全拿出一套计划,使现有项目腻滑过分到webpack
。(ps:网上的教程大多是基于单页单进口的SPA运用,和后端完全解耦的,我们的项目是和后端处于半解耦状况,而且是多页多进口,所以并不能运用大多数的webpack设置文件,须要举行变通处置惩罚)
我们先来讲说处置惩罚平常的SPA
运用的设置参数
module.exports = {
entry: {
// webpack天生的文件名和进口文件
// 我们是多页进口,先以首页为例
index: './Public/dev/page/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
},
module: {
rules: [ {
// 种种loaders
} ]
},
plugins: [
new HtmlWebpackPlugin( {
// 模板天生后的文件名(能够加上途径)
filename: 'index.html',
// 进口文件的模板(也就是承载你页面视图的处所)
template: 'index.html',
inject: true
})
]
}
抛开其他杂七杂八设置不谈,上面的设置就是大多数的SPA
运用的设置。用在我们的项目里,在根目次运转webpack
会发明发作毛病,并提醒缺乏许多的模块,由于这些我们自定义的模块webpack
自身并不能辨认,所以这里有至关主要的一步,将现有的requirejs
的设置文件里的paths
同步迁移到webpack
的设置文件里
// 在requirejs设置文件里多是如许写的
require.config( {
paths: {
header: './modules/header',
}
} );
// 那末我们就应该将此设置交给webpack
resolve: {
alias: {
header: 'modules/header' // 途径能够不一定是这个
}
}
然后我们再打包,运转,发明丫的竟然会报错了?最显著的毛病就是define is not defined
。让我们来翻翻上面我们准当代期的代码
// individual
define( [
'constants',
'utils'
], function( consts, utils ) {
var aa = consts.aa;
var bb = consts.bb;
var tempStr = utils.replaceStr('tempStr');
} );
这里的define
就是报错的缘由(webpack
有时刻并不能辨认这里,有时刻却又能准确转换成能运转的代码,没有穷究这里的缘由,虽然webpack2
已支撑AMD作风的代码打包,然则我照样决议对这里稍作修正,变成CMD作风,即使是运用CMD
作风的seajs
依然是须要去掉表面那层包裹的函数的,不管怎样都得改),因而我们只须要将上面的代码调解为:
2017年7月24日22点50分更新,经由我的尝试,只需设置依靠都准确,完全能够直接打包,没必要非得改成CMD,因而换成webpack
更轻松了~~?
var consts = require('constants');
var utils = require('utils');
var aa = consts.aa;
var bb = consts.bb;
var tempStr = utils.replaceStr('tempStr');
// 假如这里有return的话须要将return obj调解为
module.exports = obj;
至此我们再打包便能够轻轻松松兼并了(固然假如你要提取大众代码的话又是别的一个插件了,这里不再赘述)
打包宣布的题目处理了,最主要的一环开辟环境的搭建呢?
实在机灵的我早推测这类设置在我们的项目并不圆满,由于HtmlWebpackPlugin
这个插件须要的模板是放在硬盘里的静态文件模板,它会自动插进去构建好的js
和css
文件,我们的模板不是静态的,是从php后端衬着的一段动态的html,照样作死试了试,果真涌现了以下状况
- 动态援用的
header、footer
不见了 - 页面涌现一堆后端模板的语法
{$xxx}{$yyy}{$zzz}
实在webpack-dev-server
供应了一个代办功用,那这里的题目处理起来就美滋滋了。纯真的我最早的设置是如许的:
var express = require( 'express' )
var proxyMiddleware = require( 'http-proxy-middleware' )
var app = express();
// 这是代办的
var proxyTable = {
'/': {
target: 'http://xxxx.cn/'
}
}
Object.keys( proxyTable ).forEach( function( context ) {
var options = proxyTable[ context ]
if ( typeof options === 'string' ) {
options = {
target: options
}
}
// 运用代办地点和代办目的
app.use( proxyMiddleware( options.filter || context, options ) )
} )
以上代码将我们一切的要求途径一股脑悉数代办给后端php
效劳了,HtmlWebpackPlugin
这个插件会自动写入依靠的剧本文件和款式表文件,然则此时的文件是webpack-dev-server
效劳天生的,而且存在于内存里,所以此时我们再运转webpack
开启的效劳,就会形成页面出来了(包含任何动态从效劳端衬着的数据),然则款式和js
都没有加载,由于要求被代办到了后端,后端的目次里并不存在这些文件(空话么),所以我们须要过滤掉这些特定的要求不让http-proxy-middleware
插件举行代办,为了辨别这些特定的要求,我们将entry
字段里的文件名都加上一个前缀__webpack
或任何举世无双的与背景要求开首不一样的字符串,此时proxyTable
里的filter
函数就派上用场了,检察官方文档是这么形貌这个函数的
For full control you can provide a custom function to determine which requests should be proxied or not.
为了完全掌握你的要求,你能够定义一个函数来一定这些要求是不是应该被提交
因而我终究拿出一个惬意的代办设置文件,开心得我似乎升职加薪了一样?
var proxyTable = {
'/': {
target: 'http://xxx.cn/',
changeOrigin: true,
filter( pathname, req ) {
return !new RegExp( `^\/(__webpack|${assetsSubDirectory})` ).test( pathname );
}
}
}
让我来解释一下上面的代码:未匹配到以__webpack
开首的要求,都举行代办,这里增加了一个assetsSubDirectory
变量,这个变量现实上是webpack天生的图片、字体文件、json文件、svg等依然存在于内存里的援用的途径,由于在内存里跟着我们的编码能够及时更改,所以它们照样不须要做代办,直接过滤掉。
对了,遗漏了一个很主要的设置,代码以下:
plugins: [
new HtmlWebpackPlugin( {
alwaysWriteToDisk: true,
// php端运用到的模板
filename: `${ROOT}/Application/Home/View/Index/index.html`,
// 模板文件
template: `${ROOT}/Application/Home/View-template/Index/index.html`,
chunks: [ '__webpack-indexController' ],
inject: true
})
]
机灵的我们一定能发明View-template
这里的差别,见名知意,这个文件夹里的html都是对应的后端的模板视图文件,我们经由过程alwaysWriteToDisk
这个参数(实在还须要合营别的一个插件)以template
字段的值为目的,及时写入到filename
对应的文件里,而此时,由于浏览器接见的页面里由于我们启动webpack-dev-server
时已编译了这个文件,js会主动和webpack效劳竖立一个eventSource
长衔接(这个衔接也是消除在代办范围内的)来监听文件变化,所以就会自动革新浏览器,从而完成我们的live-reload
。
至此,从准当代期
到当代期
的过渡计划就算是完成了,接下面就是寻觅一个适宜的时候点实行到项目中去。若你要问我那末多页面是不是是全都一个个得配,固然是,然则为了轻易易保护,能不侵入现有项目去修正文件名,我们一定须要去手动编写一个map
映照文件,来指明我们的模板文件对应的进口文件,经由过程这个map我们再来动态天生entry
和HtmlWebpackPlugin
须要的模板途径,固然这里并非没有便利的方法,我们能够写一个剧本去读取View-template
下面的目次来自动天生map然则由于我们童鞋在定名的时刻文件夹和对应的进口文件并不能对应上,就得修正,这并非引荐的做法,而且也不轻易我们在革新代码作风的时刻举行单个调试。
上面的示例代码都不是完全的,由于我并非要供应一个webpack
的教程,而是处理后端和前端html耦合的webpack-dev-server
设置的题目。
以上内容都转自我本身的博客原文地点