一起来构建前端工具链吧~(新建项目)
好久没有更新博客了。。。不知不觉做前端已过去一年有余了,渐渐发现现在入门前端的门槛越来越高了,需要学习的东西也多了不少,工作中效率也越来越慢,那么提高自己的打码速度也就越发的重要了,而作为程序员的我们也应该把和业务无关的东西提取出来,让它自动化,简单化,这样才能事半功倍。于是便有了我做工具链的想法,经过两个版本的迭代,npm上也有3千多的下载了,也算是稳定了下来,这个系列我就来一步步的讲述整个工具链的始末吧~
首先,先贴上github地址 nuts工具链,(求关注 (☆_☆))在文档中记录了如何使用,在这里我就不再赘述了,我们就从一个个的命令开始吧。
创建新项目
我的工具链是基于gulp开发的,所以在运行的时候需要gulp命令进行操作,今天就来介绍一下 create 这个命令是如何实现的。首先看这个命令的用法:
gulp create --name xxxx
一般来说我们的项目的目录组成是由 html文件,js文件和css文件以及图片字体等静态资源组成的,那么我们在创建新项目的时候也就应该拥有这些内容了。那么这整个是怎么开始运行起来的呢?这就要从开始输入命令行的时候说起来了,在这个命令行中,create 关键字是一个任务,而这个任务接收 -- 结尾的参数,并且拿到参数的内容已交给后续的程序处理,这其实就是整个库的结构了,而最外层的gulpfile.js 文件就是我们项目的配置文件了,因为使用的是gulp3.X的版本,所以必须存在这个gulpfile文件才能正常运行gulp任务,而在这个文件中我也仅仅是引入了 controller 文件并且运行其中的 run 方法,剩下的仅仅是提供一个config的配置对象而已,那么这个 run 方法又是做什么的呢?
exports.run = ()=> { fs.readdirSync('./nuts/tasks/').forEach((files) => { if (/(\.(js)$)/i.test(path.extname(files))) { require('./tasks/' + files); } }); };
在这个方法中我循环加载了tasks文件夹下的所有文件,也就是说tasks文件夹下的每一个文件都是一个任务,同时为了支持ES6的代码,nuts中使用了webpack作为打包工具,下面这个方法返回的就是一个最基础的webpack配置:
// webpack的配置文件,一般情况下不需要修改 exports.webpackConfig = (dev)=> { return { watch: false, module: { loaders: [ { test: /\.js$/, loader: 'babel-loader', exclude: './node_modules/', query: { presets: ['es2015'] } } ] }, plugins: (dev == 'dev') ? [] : [new webpack.optimize.UglifyJsPlugin()] } };
而整个命令行会接收多少参数呢?端口号,项目名称,打包版本等等,这么多的参数,我选择采用一个对象来进行保存,这样便于管理和替换:
/** * 通过命令行传入的参数,在这里进行缓存,方便后面调用。 */ exports.arguments = ()=> { let inputArgv = minimist(process.argv.slice(2)); return { _name: inputArgv.name || inputArgv.dir, _port: inputArgv.port, _clean: inputArgv.clean, _dev: inputArgv.dev, _build: inputArgv.build, _create: inputArgv.create, _ver: inputArgv.ver, _include: inputArgv.include }; };
这段代码中的 minmist 方法是一个第三方的库,这是一个可以让node接收命令行参数的库,虽然功能简单,但是够用就好了~何况它还特别的小~
其实这个流程总结下来就是由 minmist 接收命令行中传来的参数,然后交给 controller文件去处理,然后再将各个参数传给不同的tasks去处理的流程,只不过,在这个当中,我做了一些针对自己的处理。下面我们看看这个 create 命令里面究竟做了些什么~
packages._core.task('create', () => { let proName = controller.arguments()._name || defaultName(), devDir = `${controller.config.sourceDir}/${proName}`; if (!!path.basename(proName)) { fs.exists(devDir, (exists) => { if (exists) { console.log('警告!!!您要创建的项目已经存在!'); } else { createProject('nuts/templets', devDir, path.basename(proName)); } }); } else { console.log('警告!!!这是一个需要完整路径的项目!'); } });
在这个任务初始化的时候我们首先初始化两个参数,一个是需要创建的项目名称 proName,以及这个项目相对的路径 devDir,然后检查这个项目是否已经创建,如果不存在的话就调用 createProject 方法来进行创建。
/** * 创建新项目的函数 * @param templet * @param devDir 项目路径 * @param name 项目名称 */ function createProject(templet, devDir, name) { var letter_name = name.replace(/\_(\w)/g, (all, letter)=> { return letter.toUpperCase(); }); packages._core.src(`${templet}/index.html`) .pipe(packages._replace('@@main', name)) .pipe(packages._core.dest(`${devDir}/`)); packages._core.src(`${templet}/scss/main.scss`) .pipe(packages._rename(`${name}.scss`)) .pipe(packages._core.dest(`${devDir}/scss`)); packages._core.src(`${templet}/js/main.tmpl`) .pipe(packages._replace({ '@@project_name': name, '@@author': controller.config.author, '@@date': time, '@@project': letter_name.replace(/(\w)/, v=> v.toUpperCase()), '@@letter': letter_name })) .pipe(packages._rename(`${name}.js`)) .pipe(packages._core.dest(`${devDir}/js`)); // 创建图片和字体文件夹 mkdirs(path.resolve(__dirname, `../../${devDir}/`), (err)=>{ if (err) { console.log(err); } fs.mkdirSync(path.resolve(__dirname, `../../${devDir}/images`)); fs.mkdirSync(path.resolve(__dirname, `../../${devDir}/font`)); }); console.log(name + '项目创建完成!!!'); }
这个方法接受3个参数,但是使用者只能传入后两个参数,我要做的就是创建html文件,js文件和scss文件以及images和font两个文件夹,同时在输出这些文件的时候我也会根据配置文件中的设置去替换模板中的关键字,其中需要注意的就是node在创建文件夹的时候需要如果父文件夹不存在时则会抛出错误,那么这个时候就需要递归的创建缺失的文件夹了,所以我写了 mkdirs 方法来解决这个问题。
在tasks同级的目录下有个util目录,里面放着一些非任务的脚本,通常是用来处理诸如此类的问题的:
/** * 创建多层文件夹 异步 * Created by fuhuixiang on 16-9-1. */ const fs = require('fs'), path = require('path'); module.exports = (dirpath, callback)=> { mkdirs(dirpath, ()=> { callback(); }); }; // 异步文件夹创建 递归方法 function mkdirs(dirpath, _callback) { fs.exists(dirpath, (exists)=> { if (exists) { _callback(dirpath); } else { //尝试创建父目录,然后再创建当前目录 mkdirs(path.dirname(dirpath), ()=> { fs.mkdir(dirpath, _callback); }); } }); }
通过这样一个简单的递归方式,就可以循环的创建文件夹了,到此create命令中的两点,创建文件和替换关键字的功能点都实现了,下面就是真正的使用了~下篇我将介绍真正使用最多的dev命令~
gulp create ---name test1 gulp create ---name 2016/test1