前端工程化实践for-mx(一)

前端工程化是最近总听到一个词,而在业务不断发展的过程中,传统的开发流程早已不适用于前端爆发式发展的今天。在此,针对最近的探索以及mx内部使用的基本前端工程机构进行说明。

1.我们需要什么

刚进入公司的时候,我们使用jquery + html搞定所有的页面开发,使用smarty,ajax进行前后端交互,模板渲染;没有完善的接口文档,往往是前端把UI在本地开发完成后将页面直接抛给后端进行套接,看似前后端分别负责各自的业务,但往往会在交互过程中由于对于双方逻辑的不理解导致很大的障碍。因此,我们想要将前后端进行完全分离,各自维护逻辑。

而对于庞大的、不断迭代的项目,进行各功能模块、公共模块等逻辑及静态资源的管理时,往往会因为想要避免逻辑冲突而整个模块的复制代码,导致大量的资源冗余及极差的代码可读性、维护性。因此,我们尝试使用AMD,commonJS进行模块化管理

对于常用的功能样式、小单元,我们往往会进行插件开发。而这种方式往往引用复杂,不太灵活,使用过程中需要分别引用样式、结构和逻辑单元。而vue、react等组件化思想框架的风靡让我们热衷于使用组件化方式进行开发。因此,我们更推荐使用ES6规范,使用sass进行样式表管理。导致了我们对于静态资源管理、编译、打包的需求。

同时,还有一系列诸如自动更新版本号、压缩代码、浏览器自动刷新等一系列愿望。梳理一下,大概有以下几点:

1.开发调试
  • 前后端分离。不再使用smarty模板进行渲染,全部通过ajax进行交互。
  • 组件化开发。各组件维护其自身的静态资源,在此选用vue作为组件化开发框架,.vue模板进行组员整合,实现资源内聚,降低耦合度。
  • 模块化开发。静态资源模块化管理,防止资源冗余。
  • 调试过程中自动监听变动,浏览器自动刷新,解放双手。
  • sass,es6等语言自动编译。
  • 简易的包管理引用方式
2.打包部署
  • js、css文件压缩,减少资源占用空间
  • 自动版本号控制,解决缓存问题
  • 资源自动引用,自动依赖管理
  • 区分开发、测试、生产环境
  • 同一项目,多个页面、多个入口自由配置

自动 自动!

上面的功能,都自动好吗!

2.vue-cli-webpack 模板

webpack对于我们来说应该也很熟悉了,是一个很好的模块资源加载器,将所有的资源当做模块进行处理,支持AMD、CommonJS规范。满足了我们的模块化开发要求。而对于初学者来说,其包括官方文档、使用报错等其实不是太友好,很多时候会因为基本的环境配置而很头疼。

很荣幸,vue-cli让为我们提供了各种快速开发的模板环境,为我们提供了配置好的模板直接进行使用。让我们看下其实现方式:

1.vue-loader组件化开发

通过vue-loader,我们可以很方便的将组件的样式、模板、逻辑等进行整合,实现了资源的高内聚。同时,.vue模板还配合babel、sass等loader整合了sass、es6等书写方式,让我们更加愉快的书写代码。类似这样:

vue-loader

同时我们会发现,style标签中可以添加scoped属性,实现我们所期待已久的css模块化。让组件样式不会对外部进行干扰,妙哉!

2.代码热替换

vue-cli通过使用express启用开发服务进行开发中调试,类似webpack-dev-server,同时,启用了vue-hot-loader等,实现了代码热替换功能,大大的提高了我们的开发效率。不用每次进行修改时,再一次次的刷新浏览器了。

3.打包

在打包过程中,模板还提供了以下几种功能:

  • UglifyJsPlugin: js,css代码压缩
  • CommonsChunkPlugin: 公有模块提取
  • HtmlWebpackPlugin: 自动生成页面,引用静态资源。
  • chunkhash进行版本控制管理,为每次变更自动添加版本号

4.不足

通过vue-cli,基本已经可以满足我们在开发调试和打包部署过程中的需求,让我们快速方便的进行单页面应用开发。而在实际过程中,我们需要对其进行改写,来满足实际使用过程中的不足之处:

4.1 静态资源引用问题

我们可以发现,cli在进行图片等资源打包时,通常会将其配置成’/‘,将所有图片等静态资源放在根目录下。而我们的开发方式是在服务器进行开发,根据’/‘区分页面路径,采用相对路径进行图片的引用。因此我们会将publicPath进行相应的修改,像这样:

1
2
3
4
5
6
//统一资源输出路径,和php共同放在public文件夹下
assetsRoot: path.resolve(__dirname, '../../../public'),
//静态资源存放路径
assetsSubDirectory: 'assets_default',
//页面路径通过php include进入public文件夹内,因此我们使用''或'./'即可
assetsPublicPath: ''

通过上述配置,打包后的资源会像下面展示:

html:

html正常

1
2
3
4
5
6
...
<link href=assets_default/css/app.4ca66feb389e3954236b3e2f19470797.css rel=stylesheet>
</head>
<body>
<script type=text/javascript src=assets_default/js/app.2f605124cd9607109c08.js></script>
</body>

而css文件内部引用的图片,会出现如下错误,其编译后为

1
2
3
.bg{
background: url(assets_default/img/bg.jpg);
}

而静态文件的目录是这样的:

vue-loader

因此是无法根据路径:assets_default/img/bg.jpg访问到图片的,需要使用相对路径:’../img/bg.jpg’。而统一修改publicPath为’../../‘让其访问’../../assets_default/img/bg.jpg’又会使html资源引用出错。在踌躇很长时间后,终于找到了解决方法。为ExtractTextPlugin单独设置publicPath

1
ExtractTextPlugin.extract('vue-style-loader', sourceLoader, { publicPath: '../../' })

最终代码:

1
2
3
4
5
6
7
8
9
10
//index.js 将资源引用的publicPath抽离
publicPath: '',
assetsPublicPath: '../../',
//修改util.js,更改ExtractTextPlugin
if (options.extract) {
return ExtractTextPlugin.extract('vue-style-loader', sourceLoader, { publicPath: options.publicPath || '../../' })
} else {
return ['vue-style-loader', sourceLoader].join('!')
}
4.2 build环境区分

在公司我们有开发、测试、生产三个环境,我们往往需要根据不同的环境去配置不同的打包细节以及诸如接口等不同的环境变量。在vue-cli中,提供了dev、test、prod三个文件。会导出不同的变量参数。而其只做了区分调试、打包、和单元测试三种开发方式,不是我们想要的区分不同的build环境。因此我们需要在此对其进行修改。

1.配置运行脚本
在package.json中,我们将运行脚本设置为

1
2
3
4
5
6
"scripts": {
//开发调试
"dev": "node build/dev-server.js",
//打包编译
"build": "node build/build.js "
},

因此我们可以使用npm run dev 进行调试, 使用npm run build + [参数] 进行不同环境的打包

2.接收打包环境参数
我们需要获取命令参数来进行不同的配置文件的引用,具体修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//获取环境参数
var options = process.argv[2];
//开发端口
if (options && !isNaN(options) && options >= 10000 && options < 65535) {
var port = options;
}
//引用对应的环境配置文件
var envopt = path.resolve(__dirname, './' + options + '.env');
if (!fs.existsSync(envopt + '.js')) {
envopt = path.resolve(__dirname, './dev.env');
};

通过不同的参数,我们可以获得对应的配置文件,如dev.env.js or test.env.js,他们大概包括以下内容,可根据个人口味进行添加:

vue-loader

3.配置相关变量,并暴露全局。

  • 我们通过DefinePlugin定制不同环境代码:

    1
    2
    3
    4
    5
    6
    var env = config.build.env;
    new webpack.DefinePlugin({
    'process.env': {
    NODE_ENV: env.NODE_ENV
    })
  • 通过ProvidePlugin将环境参数暴露全局,方便直接引用,而不用在变更环境时,手动修改代码:

    1
    2
    3
    4
    //暴露接口变量
    plugins.push(new webpack.ProvidePlugin({
    ENV_OPT: config.build.envopt
    }));
  • 根据不同环境判断是否压缩等,方便其他环境调试

    1
    2
    3
    4
    5
    6
    7
    if(env.isUglyfy){
    plugins.push(new webpack.optimize.UglifyJsPlugin({
    compress: {
    warnings: false
    }
    }))
    };

在代码中,我们可以直接通过 ENV_OPT[变量名]获取你想要的,区分环境的参数。

4.3自由配置多个页面

我们会发现,vue-cli默认使用/index.html作为模板,自动引用所有入口文件,适合单页面应用使用。而我们在日常工作中,往往会有很多诸如运营活动,管家各种h5内嵌页面。我们不希望其单独的存在一个项目内,而是在一个通用的工程中进行书写,并且使用vue进行组件化开发,将通用功能进行抽离,进行快速迭代。因此我们需要将其改写为支持多个入口页面配置,并灵活的配置其输出路径等。

首先,我们还是将所有的配置都放在config/index.js里,方便在使用过程中进行修改,我们需要有一个地方来配置我们各页面的各种属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//编译入口文件,包括所有入口内容
entry: {
index: './src/views/main.js',
// test: './src/views/test.js', //第二个页面入口
},
html: [
{
filename: 'index.html', //模板输出名称
title: '测试页面', //页面title
path: '../tpl/', //模板输出路径,默认在'../tpl/'下
entrys: ['index'], //页面需要引用的js入口文件
//模板名称,默认:index.html 文件目录:src/tmpl/index.html,
//若文件目录为src/tmpl/test/index.html,则写test/index.html
tmpl: 'test.html'
},
/*第二个页面
{
filename: 'test.html',
path: '../tpl/test/',
entrys: ['test'],
}*/
],

我们可以把各个页面的配置在html字段中进行管理,包括输出路径、输出名称,以及各页面需要引用的entry。默认状态下,会使用src/tmpl/index.html做为模板,若我们需要使用自定义模板进行书写时,直接在tmpl/文件夹下进行书写,并修改对应字段即可。

紧接着,我们使用htmlWebpackPlugin进行梳理,输出对应页面:

vue-loader

每次通过npm run build [env] 即可自动打包出各页面,并引用其资源文件。

总结

通过对vue-cli的webpack模板进行修改,我们已经理出了一个基本满足我们需求的开发调试、打包环境,配合后端目录结构约束,已经可以进行实际的输出。通过工程化管理,我们可以针对不同页面引用很多通用的组件,自动管理依赖、打包等问题。本章节介绍了具体的改动及思路,下一篇文章我们将重点介绍如何进行使用。

最后,前端工程化还是一个很遥远的路程,我们的实践仅仅只是工程化的一小部分,愿我们在不断实践的道路上越走越远!