随着年初开始使用webpack重构公司的广告代码,已经有将近一年的时间了,需求也渐渐的稳定了。我想也是时候将这几个工程整理一下,顺带着处理一些历史问题。
由于当年各个业务线没有整合、需求也没有固定,考虑到将来随着不同业务线的发展方向不同,我为不同业务线的广告代码创立了不同的git工程,分别开发。但是我仍然为他们采用了相同的项目配置和基础逻辑,为将来有一天需要整合的时候做准备。这就导致了在不同业务线的广告代码中我有很多相同的基础模块,一旦修改这些模块,就要分别手动同步一遍,费时费力。因此这一次的优化的第一要务就是合并工程。另外采用webpack开发,我日后加入es6模块后需要source-map来方便调试。最后就是进来使用vue-cli的感悟,我原有的工程太依赖gulp,在阅读了vue-cli生成的webpack工程后,我打算尝试将项目中的gulp替换成npm script。
因此我这次优化的任务就有了以下的几条:
1、工程合并:webpack多入口;
2、soucemap:加入sourcemap选项,并区分开发联调环境和上线环境;
3、npm script替换gulp;
4、bug修改;
一、工程合并、多入口
最开始的时候将pc和wap的广告代码分别配置了两个git仓库,由于业务模式是差不多的,所以才用了相似的架构,不同点在于两端的广告特型是不同的、浏览器适配方案不同,这就导致了两份代码中有大量的相同的模块,每次修改这些相同的模块时要同事修改另一个,当是的做法是将这些模块猎在一个json文件中,一旦有修改就跑一段gulp脚本同步一下。但是这样做终究不是办法。这本身就是一个很不合理的方案,那么我为什么要分成两个。原因是长期以来PC的后台和wap的后台长期由两个不同的后端组编写,而且后端架构分别由PHP和Java实现,不同业务线的维护工作有可能拆分到别的部门来维护,这是一个历史原因。正是基于这个原因,我没有将两个藕合在一起,以方有一天我需要将两份代码拆开交给不同的团队来维护。
但是我仍然考虑了有一天会合并在一起,所以一直强行规范这两个项目中逻辑结构和相同的功能模块保持同步,这为我能够在未来的某一天可以顺利的将两个工程合并以及当出现新业务线时,可以快速编译出一份新的广告代码,我的代码一定要能支持不同的场景,并不为了某一个业务线而产生。事实上随着广告业务资源的整合与稳定,我现在打算做出这种尝试。
我在最初选用webpack也是出于多入口,理论上我只需要将wap中不同的入口模块和业务特型拷贝到PC,然后修改webpack配置文件,就可以实现合并。
那么我简单说一下webpack的多入口配置(我先给出官方文档):
我们要关注的主要是webpack.config.js,当然你的工程中webpack配置文件可能不叫这个名字。以我的为例:
1 var path = require('path'); 2 var webpack = require('webpack'); 3 //var HtmlwebpackPlugin = require('html-webpack-plugin'); 4 //定义了一些文件夹的路径 5 var ROOT_PATH = path.resolve(__dirname); 6 var APP_PATH = path.resolve(ROOT_PATH, './src/app'); 7 var BUILD_PATH = path.resolve(ROOT_PATH, './src/build'); 8 // var NODE_PATH = path.resolve(__dirname, 'node_modules'); 9 10 module.exports = { 11 //项目的文件夹 可以直接用文件夹名称 默认会找index.js 也可以确定是哪个文件名字 12 entry: { 13 "yourcodepcv1": APP_PATH + '/index.js', 14 "yourcodewapv1": APP_PATH + '/index-wap.js' 15 }, 16 //输出的文件名 合并以后的js会命名为adsfehomev1.js 17 output: { 18 path: BUILD_PATH, 19 filename: '[name].js'//, 20 // sourceMapFilename: '[file].map', 21 // devtoolLineToLine: true 22 }, 23 devtool: '#source-map', 24 externals:{ 25 jquery: "jQuery" 26 } 27 };
其实,很简单。我们一起来看一下,主要看module.exports中的entry和output,entry接收我们传入一个对象如下图,每一组的键值对都是一个入口,我这里写了两个入口文件。
但是webpack并不允许我们传入一个好几组输出键值对。事实上入口中的每一个键值对在webpack中被视为一个“chunk”,我们在ouput中的[name]会被入口中的键名替换,可替换的不仅仅是键名,我们还可以加入[id]、[hash]、[chunkhash]等。
实际运行webpack后会看到终端中如下信息:
其中有一列chunks,看到我们有两个chunk: 0和1。 0是我的pc代码,1是wap的。而对应的source-map也不会新建chunk。输出是也用了我们的[name]。
实际开发中,当然不仅仅是入口不同,webpack通过require关键字查找文件,如果不同入口饮用了同一个模块,但是这个模块却依赖pc和wap各自的模块,我们可以在入口处定义好一个map缓存到一个通用的数据模块,这个模块的数据并不会硬编码,而是由入口处传入。也可以是各写各的,仅仅把复用的模块提出来饮用。但是这样做虽然简单但是当我想在加入一个入口时,就要写一溜文件,并不如入口定义map这种方便,加一个文件就行。当然方法有很多,条条大路通罗马,js语法是很强大的。
二、sourcemap
这个其实比较简单,webpack是支持的,看我上面的配置文件,就可以看到 :
devtool: '#source-map'
看过我第一篇文章的朋友会看到,我最开始是用requireJS写的,好处在于开发时文件有浏览器异步加载,调试的时候找到那个文件就比较好调试。可是现在不同了我用webpack在nodeJS环境预编译打成一个文件。试想一下文件挺大的,而且和我写的并不是完全一样,找个东西不太方便,何况我用ES6写的部分更是被转化了。我需要souce-map来告诉我应该对应源文件的那一行。
三、npm script代替gulp
我在开始写的时候直接用了gulp,不得不承认gulp很强大,而且很方便。可是我要做的事情并没有那么复杂,我需要的主要是:多任务、不用记不同的命令、webpack打包、mocha测试、jslint检查、开发环境变异、上线环境打包、部署等。其实不需要太深厚的nodejs功力,即使不实用gulp也可以搞定。
其实我这种想法来自于我前段时间写vue,当时我用到了vue官方的vue-cli,对 就是那个脚手架里面虽然用了webpack,可是没有用gulp、grunt等工具。取而代替,用的是npm script。用过的同学会了解,就是package.json中那个“scripts”快捷命令。其实从另一个方面来说gulp和jQuery都是让人又爱又恨的工具,好的是确实方便,不好的是这些工具让我们对原生技术的原理、机制有了错误的理解。而且npm的包要比gulp的包要多很多,一般来说,功能都是在npm有了用着不错,想在gulp中用才会被人封装成gulp-XXX包。相应的更新维护速度,谁比较快就比较明显了。
下面我就简单的以我的工程为例子介绍一下,如何使用npm script配置工程:
就一我dev(开发环境,watch编译)和build(打包、压缩)为例:npm run dev;npm run build就可以调用。
对于我来说开发环境只要在终端输入webpack就可以了。而我原来是怎么做的:为了让gulp能够控制整个流程,我是在gulp中require("webpack")而是利用gulp的watch,这么做的缺点在于为了用gulp无意中让webpack的使用更加繁琐,而且gulp的watch相当于gulp取监听文件变化,在取调用webpack,如果写的不好每次相当于从新运行webpack,ES5还好,如果用了很多loader,比如ES6 bable,就会很慢(10s以上,不能算是即时编译)。而webpack自带的 -w不同,第一次慢一点,之后就缓存在内存中了,即使是ES6,也会很快。
问题来了,我build为什么不想dev那样写?因为build不仅仅用到了webpack,而且用了不同的webpack配置。我把要做的事情封装到minitodist.js中,并且因为build任务和cdn任务很像,我在调用npm run build的语句中传入了一个参数‘dist’。那么有什么不同呢:
1、调取不同的webpack配置;
2、修改版本变量;
3、根据参数输出到不同文件夹;
其实webpack配置仅仅是一部分不同,所以我用了webpack-merge来生成build用的配置:
1 var path = require('path') 2 var webpack = require('webpack'); 3 var merge = require('webpack-merge'); 4 var baseWebpackConfig = require('./webpack.config'); 5 var ROOT_PATH = path.resolve(__dirname); 6 var APP_PATH = path.resolve(ROOT_PATH, './src/app'); 7 var BUILD_PATH = path.resolve(ROOT_PATH, './dist'); 8 9 var modifiedDate = +(new Date()); 10 11 var webpackConfig = merge(baseWebpackConfig, { 12 devtool: false, 13 output: { 14 path: BUILD_PATH, 15 filename: '[name].min.js', 16 }, 17 plugins: [ 18 new webpack.optimize.UglifyJsPlugin({ 19 compress: { 20 warnings: false 21 } 22 }), 23 new webpack.BannerPlugin('This file is modified at:' + modifiedDate) 24 ] 25 }); 26 27 module.exports = webpackConfig
先写var baseWebpackConfig = require('./webpack.config');来获取webpack.config.js。再调用webpack-merge模块创建新的配置,通过exports输出。
比如我想要在线上环境使用已经压缩的代码。我就在新的配置中的“plugins”中加入webpack.optimize.UglifyJsPlugin,并且在输出时将文件名改成XXX.min.js(也可以不加min)。我也可以在文件头加入有关版本的注释(webpack.BannerPlugin)。
关于node环境调用文件的参数的问题。当你用如下语句:
node xx.js ‘abc’
在xx.js中可以这样拿到:(具体原理就不解释了)
var arguments = process.argv.splice(2); console.log(arguments[0]);
如package.json文件我陆续的写了别的任务。目前是不需要使用gulp的,对比一下我前后的依赖列表:
之前:之后:
是不是感觉少了很多。
总结:不管部门的调整后,我是否还会维护这套广告代码,我仍希望交给别人一套易读的容易维护的工程,我希望我写的不仅仅是程序,而是工程,有始有终。