本文不是npm、bower、grunt的初级入门教程,而是希望给一个非常合适团队使用的前端项目构建模板,让不熟悉该部分内容的团队成员可以边开发、边应用、边理解,节约时间的同时还能更专注于具体业务。
前言
就如同还有不少做java的程序猿&攻城狮们居然还没用上maven一样,不少偏重前端开发的程序猿&攻城狮也还没有拥抱npm、bower这样的包管理、构建工具,本文根据自己需要和用途总结的前端标准架构。经常看国外大神们源码的同学一定对这些概念不陌生,具备maven、gradle基础则更容易上手。
开门见山
使用npm、bower、grunt的目的
- 开发规范化、标准化
- 提高开发效率,尽可能多的提供自动化辅助功能,比如语法检查、文件复制、压缩、css编译等等
什么是npm、bower、grunt
比较官方的解释:
名称 | 描述 |
---|---|
npm | Node.js的包管理工具 |
bower | 基于github的软件包管理器 |
grunt | Grunt是基于Node.js的项目构建工具 |
下面给出我自己定义的三者在前端构建中其扮演角色的白话文版本:
名称 | 描述 |
---|---|
npm | bower和grunt运行的基础环境,如同jvm与java应用程序的关系 |
bower | SPA项目用到的组件,如jquery,d3等等 |
grunt | 可编程的自动化构建工具 |
上述表述还是不太好理解,下面再简化一次
名称 | 描述 |
---|---|
npm | 当它透明,不存在 |
bower | 用什么外部组件就加什么内容,如jquery,easyui等等 |
grunt | 自动化完成js语法检查、css编译、js文件压缩、文件复制合并等工作 |
安装步骤
内容较多,考虑仅补充适合linux的部分,当前暂略
常用命令
暂略,后补
目录结构组成
下面是一个比较标准的基于npm、bower进行构建的目录结构:
名称 | 类型 | 描述 |
---|---|---|
app | dir | SPA应用程序源码目录 |
bower_components | dir | bower组件存放目录,比如jquery、bootstrap等等 |
dist | dir | SPA应用程序打包输出目录 |
node_modules | dir | npm组件目录,比如grunt等等 |
.jshintrc | file | js语法检查配置文件 |
bower.json | file | bower组件描述文件,bower_components中的内容由本文件决定 |
Gruntfile.js | file | grunt构建工具的任务文件 |
package.json | file | npm组件描述文件,node_modules中的内容由本文件决定 |
项目管理工具配置
上述目录结构中bower_components和node_modules一定需要被git、svn等源码管理工具屏蔽的,因为其内容多而杂,而dist作为编译后的应用程序,是否需要屏蔽可以根据具体需要而定,下面给出git适合一般使用的.gitignore文件配置:
logs/*
!.gitkeep
node_modules/
app/bower_components/
bower_components/
tmp
.tmp
.DS_Store
.idea
dist
注:上述配置中dist是被屏蔽的
文件内容:
bower.json
{
"name": "myapp",
"description": "A starter project for backbone",
"version": "0.3.0",
"license": "MIT",
"private": true,
"dependencies": {
"requirejs-text": "2.0.x",
"requirejs": "2.1.x",
"bootstrap": "~3.3.5",
"backbone": "~1.2.3",
"handlebars": "~4.0.5",
"requirejs-domready": "~2.0.1",
"highcharts": "~4.1.9",
"font-awesome": "~4.5.0",
"d3": "~3.5.11"
},
"devDependencies": {},
"appPath": "app",
"moduleName": "myApp"
}
重要节点解释:
名称 | 描述 |
---|---|
dependencies | 本应用程序依赖的外部组件,分为名称和版本号,该信息都来自于github,版本号前缀字符有‘~’、‘>=’等等,暂时将其统一理解为必须满足该版本号的要求即可 |
devDependencies | 内容格式与dependencies完全一样,但只用于开发阶段 |
appPath | 指定应用程序存放的路径,本例中指向了当前目录下的app,与目录结构中的app是一致的 |
.jshintrc
.jshintrc是grunt的jshint插件的配置文件,该插件能够对js文件进行语法检查,具体配置项非常多,下面是我一直使用的内容,大体意思是打开严格检查、结尾需要‘;‘等等,一般情况下一个团队改一次就够了。需要修改时建议查阅官方文档。
{
"globalstrict": true,
"boss":false,
"sub":true,
"strict": true,
"undef": true,
"eqeqeq": true,
"eqnull": true,
"laxbreak":true,
"laxcomma":true,
"regexp":true,
"plusplus":true,
"asi": false,
"newcap": true,
"bitwise": true,
"curly": false,
"expr":true,
"browser": true,
"node": true,
"latedef": false,
"noarg": true,
"-W032":false,
//"es5": true,
//"esnext": true,
//"proto": true,
"smarttabs": true,
"jasmine": true,
"predef": [
"require",
"define",
"notify"
]
}
Gruntfile.js
'use strict';
module.exports=function(grunt){
var appConfig = {
app: require('./bower.json').appPath || 'app',//重点:对应目录结构中的app目录
dist: 'dist' //重点:对应目录结构中的dist目录
};
//任务配置,所有插件的配置信息
grunt.initConfig({
// Project settings
yeoman: appConfig,
//获取package.json的信息
pkg:grunt.file.readJSON('package.json'),
//jshint语法检查配置,Gruntfile.js就是被该插件调用
jshint:{
build:['Gruntfile.js','<%= yeoman.app %>/*.js'],
options:{
jshintrc:'.jshintrc'
}
},
bower: { //由grunt调用bower时需要的参数
install: {
options: {
"targetDir": "<%= yeoman.dist %>/lib",
"layout": "byComponent",
"install": true,
"verbose": false,
"cleanTargetDir": true
}
}
},
// 重点:清理dist目录
clean: {
dist: {
files: [{
dot: true,
src: [
'.tmp',
'<%= yeoman.dist %>/{,*/}*',
'!<%= yeoman.dist %>/.git{,*/}*'
]
}]
},
server: '.tmp'
},
// 重点:复制文件到dist目录下,这个部分经常会根据项目的需要进行修改,但总体上还是固定的
copy: {
dist: {
files: [{
expand: true,
dot: true,
cwd: '<%= yeoman.app %>',
dest: '<%= yeoman.dist %>',
src: [
'*.{ico,png,txt}',
'.htaccess',
'*.js',
//'*.html',
'images/{,*/}*.{webp}',
'styles/fonts/{,*/}*.*'
]
}, {
expand: true,
cwd: '.tmp/images',
dest: '<%= yeoman.dist %>/images',
src: ['generated/*']
},{
expand: true,
cwd: 'bower_components/bootstrap/dist',
src: 'fonts/*',
dest: '<%= yeoman.dist %>'
},{
expand: true,
cwd: 'bower_components/bootstrap/dist/css',
src: '*.min.css',
dest: '<%= yeoman.dist %>/styles'
},{
expand: true,
cwd: 'bower_components/font-awesome',
src: 'fonts/*',
dest: '<%= yeoman.dist %>'
},{
expand: true,
cwd: 'bower_components/font-awesome/css',
src: '*.min.css',
dest: '<%= yeoman.dist %>/styles'
}
]
},
styles: {
expand: true,
cwd: '<%= yeoman.app %>/styles',
dest: '<%= yeoman.dist %>/styles/',
src: '{,*/}*.css'
},
scripts:{
expand: true,
cwd: '<%= yeoman.app %>/scripts',
dest: '<%= yeoman.dist %>/scripts/',
src: '{,*/}*.js'
},
views:{
expand: true,
cwd: '<%= yeoman.app %>/views',
dest: '<%= yeoman.dist %>/views/',
src: '{,*/}*.*'
},
routers:{
expand:true,
cwd: '<%= yeoman.app %>/routers',
dest: '<%= yeoman.dist %>/routers/',
src: '{,*/}*.*'
}
},
targethtml: {
dist: {
src: 'app/index.html',
dest: 'dist/index.html'
}
},
replace: {//重点:将源码中的字符进行替换,可以使得支持开发中调试所见即所得
dist: {
options: {
patterns: [
{
match: /\.\.\/bower_components/g,
replacement: 'lib'
}
]
},
files: [
{expand: true, flatten: true, src: ['app/require-config.js'], dest: 'dist/'}
]
}
},
watch:{
scripts: {
files: ['src/*.js'],
tasks: ['jshint']
}
}
});
//重点:插件注册区,上述所有插件都在这里完成注册,当需要使用其他插件是,这里需要将其引入
//grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
//grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-bower-task');
grunt.loadNpmTasks("grunt-contrib-clean");
grunt.loadNpmTasks("grunt-targethtml");
grunt.loadNpmTasks("grunt-replace");
//注册任务,使支持命令行调用
//default任务只进行js代码验证
grunt.registerTask('default',['jshint']);
//build任务完成整个构建(本例中没有加入test部分),其实质为按顺序调用上述插件,如果一个插件执行出现异常,则整个任务失败
grunt.registerTask('build',['jshint','clean','bower','copy','targethtml','replace']);
};
package.json
{
"name": "angular-requirejs-seed",
"private": true,
"version": "0.3.0",
"description": "AngularJS seed project modified for RequireJS support.",
"repository": "https://github.com/tnajdek/angular-requirejs-seed",
"license": "MIT",
"devDependencies": {
"bower": "^1.3.1",
"grunt-bower-task": "^0.4.0",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-copy": "^0.8.0",
"grunt-contrib-jshint": "^0.11.2",
"grunt-contrib-requirejs": "^0.4.4",
"grunt-contrib-watch": "^0.6.1",
"grunt-replace": "^0.9.2",
"grunt-string-replace": "^1.2.0",
"grunt-targethtml": "^0.2.6",
"grunt-usemin": "^3.0.0",
"grunt-wiredep": "^2.0.0",
"http-server": "^0.6.1",
"jasmine-core": "^2.3.0",
"protractor": "^1.1.1",
"requirejs": "^2.1.15",
"shelljs": "^0.2.6",
"grunt": "^0.4.5"
},
"scripts": {
"postinstall": "bower install",
"prestart": "npm install",
"start": "http-server -a localhost -p 8000 -c-1 app",
"pretest": "npm install"
}
}
在package.json中预定义了很多指令,如start、test等,其实质为npm run 命令的简写,执行的化只需要输入npm [指令名称]即可,比如npm start就会启动http server并在localhost:8000这个位置提供http服务,具体使用时还需要查阅npm官方手册
常用命令
在项目所在目录依次执行下述命令即可快速初始化和项目构建工作。
初始化:
npm update //根据package.json生成node_modules目录
bower update //根据bower.json生成bower_components目录
项目构建
grunt build //根据Gruntfile.js中的配置进行项目架构
安装移除组件
bower install jquery --save //加入jquery组件,其会保存在bower_components中
bower uninstall jquery --save //移除jquery组件
日常工作中执行最多的是grunt命令,用其完成项目构建,而npm update和bower update经常是在需要初始化组件或者更新组件时才需要执行。
总结
前端的构建与java的maven、gradle其实是同样的思路,而且该项工作可以被团队、项目标准化。本文最重要的目的是给出一个适合的SPA应用项目构建结构,形成内部规范。从而使团队合作更加协调,更容易聚焦于用户体验、业务逻辑等具体工作中。本文中给出的方案没有给出文件合并、压缩等指令,而scss等自动生成我将其放在了开发工具中(比如webstorm),这个读者可以自行实验下。合适的工具做合适的事,最终达到思路清晰、事半功倍的效果。但文中还缺少一个非常重要的grunt部分:test!前端test编写也是非常重要的部分,考虑单独写一篇来描述。