文章说明:本文章为拉钩大前端训练营所做笔记和心得,若有不当之处,还望各位指出与教导,谢谢 !
一、自动化构建
简介:
自动化指的是机器代替手工完成工作,构建可以理解为转换,就是把一个东西转换为另外的一些东西。总的来说自动化构建就是把开发阶段写出来的源代码自动化去转换成生产环境当中可以运行的代码或者程序,这样的转行的过程称为自动化构建工作流,这样做是尽可能让我们脱离运行环境兼容带来的问题。在开发阶段去使用一些提高效率的语法、规范和标准,比如开发网页应用时,可以运用ECMAScript NEXT提高编码效率和质量,利用sass增强css的可编程性,再去借助模板引擎抽象页面中重复的html,这些用法大都不被浏览器直接支持。这些不被支持的代码特性转换成能够直接运行的代码,这些就可以在我们开发过程当中,通过这些方式提高我们编码效率了。
自动化构建初体验:
一开始是手写css,然后再运行在浏览器:
我们通过sass 构建:
- 安装sass模块,将其作为开发依赖安装
yarn add sass --dev //或者 npm install sass --save-dev
- 使用命令将sass文件转换为css文件
将scss文件转换后放在css文件夹下
.\node_modules\.bin\sass scss/main.scss css/style.css
- 上面的命令过于繁琐,我们可以简化下,使用NPM Scripts,包装构建命令,实现自动化构建工作流的最简方式
然后,在命令行界面使用包装后的命令,将sass文件转换为css文件:
yarn build //或者 npm run build
注意:yarn build 的run可以省略,而npm不可以
- 安装browser-sync模块,用于启动一个测试服务器,使其运行我们的项目
yarn add browser-sync --dev// 或者 npm install browser-sync --save-dev
然后使用NPM Scripts,进行包装命令,如下图所示:
最后,在命令行界面使用包装后的命令,启动测试服务器,然后唤起我们的浏览器,运行我们的网页。
yarn serve //或者npm run serve
为了使我们在启动serve命令之前,让build命令去工作,我们可以添加一个preserve命令,会自动在serve命令执行之前去执行,这个时候再去执行serve,它就会自动化的先去执行build命令,build完成之后再去执行对应的serve
还可以在sass命令添加一个 --watch的参数,然后sass在工作时就会监听文件的变化,一旦代码当中的文件发生改变,他就会自动被编译
但是执行serve时,sass会等待文件的变化阻塞serve命令的执行:
这样导致了后面的browse sync 在后面无法工作,这种情况下就需要同时执行多个任务,这里可以借助于npm-run-all这个模块去实现,先安装这个模块:
yarn add npm-run-all --dev
有了这个模块之后,就可以在scripts当中再去添加一个新的命令,这个命令叫做start,这个命令当中我们通过npm run all 里面的run-p的这个命令同时去执行build和serve命令,在命令行当中执行:
yarn start
此时尝试修改sass文件里内容,css文件也会跟着发生变化
我们在browser-sync里面添加--files \"css/*.css\",这个参数可以在browser-sync启动过后去监听项目下的一些文件(此时是css文件)的变化,一旦文件发生变化,browser-sync会将这些文件的内容自动同步到浏览器从而更新浏览器的界面,刷新效果,避免了修改代码之后再手动刷新浏览器了
小总结:scripts中build命令中去自动监听sass文件变化去编译sass,browser-sync它启动一个web服务,当文件发生变化以后去刷新浏览器。
常用的自动化工具:
相对于复杂的工程,npm scripts 就相对吃力,就需要更为专业的工具,用的最多的开发工具主要有:
- Grunt,它的插件几乎可以帮你自动化的完成任何你想要做的事情,但是由于它的工作过程是基于临时文件去实现的,所以构建速度较慢
- Gulp,它很好的解决了grunt构建速度非常慢的问题,它的构建过程都是在内存当中完成的,相对于磁盘读写速度就快了很多,另外默认支持同时执行多个任务,相对于graunt更加直观易懂,插件生态也比较完善,目前最流行的构建系统了
- FIS,相对于前两种微内核的构建系统,FIS更像是一种捆绑套餐,它把我们项目当中一些典型的需求尽可能都集成在内部,例如可以很轻松的处理资源加载,模块化开发, 代码部署,资源优化等,初学者FIS更适合。
严格来说webpack是一个模块打包工具。
二、Grunt基本使用
- 新建一个空项目(空文件夹),使用yarn初始化,生成package.json文件
yarn init --yes
- 通过yarn命令添加grunt模块
yarn add grunt
- 在项目根目录下添加gruntfile文件,这个文件是Grunt 的入口文件:
code gruntfile.js
// Grunt 的入口文件
// 用于定义一些需要 Grunt 自动执行的任务
// 需要导出一个函数
// 此函数接收一个 grunt 的形参,内部提供一些创建任务时可以用到的API
module.exports = grunt => {
//registerTask 去注册一个任务,第一个参数去指定任务的名字,第二个参数去指定一个任务函数,也就是当任务发生时自动执行的函数
grunt.registerTask('foo',() =>{
console.log('hello grunt~')
})
}
- 在控制台使用yarn grunt命令输出:
yarn grunt foo// foo 是任务名
- 还可以添加多个任务,使用命令查看帮助:
yarn grunt --help
// Grunt 的入口文件
// 用于定义一些需要 Grunt 自动执行的任务
// 需要导出一个函数
// 此函数接收一个 grunt 的形参,内部提供一些创建任务时可以用到的API
module.exports = grunt => {
//registerTask 去注册一个任务,第一个参数去指定任务的名字,第二个参数去指定一个任务函数,也就是当任务发生时自动执行的函数
grunt.registerTask('foo',() =>{
console.log('hello grunt~')
})
grunt.registerTask('bar','任务描述',() =>{
console.log('other task~')
})
}
控制台:
- 可以注册一个默认任务,在yarn grunt时,将自动调用default
// Grunt 的入口文件
// 用于定义一些需要 Grunt 自动执行的任务
// 需要导出一个函数
// 此函数接收一个 grunt 的形参,内部提供一些创建任务时可以用到的API
module.exports = grunt => {
//registerTask 去注册一个任务,第一个参数去指定任务的名字,第二个参数去指定一个任务函数,也就是当任务发生时自动执行的函数
grunt.registerTask('foo',() =>{
console.log('hello grunt~')
})
grunt.registerTask('bar','任务描述',() =>{
console.log('other task~')
})
grunt.registerTask('default',() =>{
console.log('default task~')
})
}
若是将foo和bar的任务放到自动任务的任务里,会自动的被串联执行
// Grunt 的入口文件
// 用于定义一些需要 Grunt 自动执行的任务
// 需要导出一个函数
// 此函数接收一个 grunt 的形参,内部提供一些创建任务时可以用到的API
module.exports = grunt => {
//registerTask 去注册一个任务,第一个参数去指定任务的名字,第二个参数去指定一个任务函数,也就是当任务发生时自动执行的函数
grunt.registerTask('foo',() =>{
console.log('hello grunt~')
})
grunt.registerTask('bar','任务描述',() =>{
console.log('other task~')
})
// grunt.registerTask('default',() =>{
// console.log('default task~')
// })
grunt.registerTask('default',['foo','bar'])
}
- grunt当中支持异步任务,我们在这个任务当中通过setTimeout模拟异步任务:
// Grunt 的入口文件
// 用于定义一些需要 Grunt 自动执行的任务
// 需要导出一个函数
// 此函数接收一个 grunt 的形参,内部提供一些创建任务时可以用到的API
module.exports = grunt => {
//registerTask 去注册一个任务,第一个参数去指定任务的名字,第二个参数去指定一个任务函数,也就是当任务发生时自动执行的函数
grunt.registerTask('foo',() =>{
console.log('hello grunt~')
})
grunt.registerTask('bar','任务描述',() =>{
console.log('other task~')
})
// grunt.registerTask('default',() =>{
// console.log('default task~')
// })
grunt.registerTask('default',['foo','bar'])
grunt.registerTask('async-task',() =>{
setTimeout(() =>{
console.log('async task working~')
},1000)
})
}
在命令行运行该异步任务,然后会发现控制台延迟打印:
grunt代码默认支持同步模式,如果需要异步操作,必须要使用this的async方法得到一个回调函数在异步操作完成过后去调用这个回调函数
grunt.registerTask('async-task',function(){
const done = this.async()
setTimeout(() =>{
console.log('async task working~')
done()//表示下这个任务已经完成了,grunt就知道这个是个异步任务,会等待done的执行,直到done执行,grunt才会结束这个任务的执行
},1000)
})
Grunt标记任务失败
如果在构建代码当中发生错误,例如需要的文件找不到了,可以将这个任务标记为失败的任务,在函数当中return false实现:
module.exports = grunt => {
grunt.registerTask('bad',() =>{
console.log('bad workding~')
return false
})
}
控制台运行该任务,会显示任务失败,如果说这个任务是在一个任务列表当中,这个任务失败后,后面的任务会无法执行,例如下面的代码,bar任务无法执行:
module.exports = grunt => {
grunt.registerTask('bad',() =>{
console.log('bad workding~')
return false
})
grunt.registerTask('foo',() =>{
console.log('foo task~')
})
grunt.registerTask('bar',() =>{
console.log('bar task~')
})
grunt.registerTask('default',['foo','bad','bar'])
}
上面的控制台显示bar任务没有执行,若是使用--force会强制执行所有任务:
yarn grunt default --force
如果是个异步任务,则没有办法用return false 标记,需要在done里面加入false标记为一个失败的任务:
grunt.registerTask('bad-async',function (){
const done = this.async()
setTimeout(() => {
setTimeout(() => {
console.log('bad async')
done(false)
},1000)
})
})
Grunt的配置方法
grunt.initConfig()是用来添加一些配置选项的API,接收一个 { } 对象形式的参数,对象的属性名(键),一般与任务名保持一致; 值可以是任意类型。
module.exports = grunt => {
grunt.initConfig({
foo:'bar'
})
//有了上面的配置属性之后,就可以在下面的任务当中使用这个配置属性
grunt.registerTask('foo',() =>{
console.log(grunt.config('foo'))//接收一个字符串参数,这个字符串参数是在config当中所指定的属性的名字
})
}
属性值是个对象时:
module.exports = grunt => {
grunt.initConfig({
foo:{//属性值如果是个对象的话
bar:123
}
})
//有了上面的配置属性之后,就可以在下面的任务当中使用这个配置属性
grunt.registerTask('foo',() =>{
console.log(grunt.config('foo.bar'))//可以拿到对应的属性值
})
}
Grunt 多目标任务
Grunt支持多目标模式的任务,可以理解成子任务的概念,这种形式的任务在后续具体实现各种构建任务时有用:
module.exports = grunt => {
//多目标模式,可以让任务根据配置形成多个子任务
//接收两个参数,第一个参数是任务的名字,第二个参数是一个函数(任务执行过程所需要做的事情)
grunt.registerMultiTask('build',function(){
console.log('build task')
})
}
在控制台运行该任务时,会报错,表示未给我们的build任务设置一些targets,这是因为设置多目标任务时,我们需要为这种多目标的任务配置不同的目标,通过grunt的init方法中的config去配置:
module.exports = grunt => {
//多目标模式,可以让任务根据配置形成多个子任务
grunt.initConfig({
build:{
css:'1',
js:'2'
}
})
//接收两个参数,第一个参数是任务的名字,第二个参数是一个函数(任务执行过程所需要做的事情)
grunt.registerMultiTask('build',function(){
console.log('build task')
})
}
控制台运行后会发现有两个子任务执行了,其实不叫子任务,在grunt当中叫多目标,也就是build任务有两个目标,一个js目标,一个css目标
若是想单独运行一个目标,可以在命令行后面加上对应的目标名:
yarn grunt build:css
在我们这个任务当中,可以通过this拿到当前目标的名称,通过data拿到这个目标对应的数据,然后在控制台运行任务的对应目标:
module.exports = grunt => {
//多目标模式,可以让任务根据配置形成多个子任务
grunt.initConfig({
build:{
css:'1',
js:'2'
}
})
//接收两个参数,第一个参数是任务的名字,第二个参数是一个函数(任务执行过程所需要做的事情)
grunt.registerMultiTask('build',function(){
console.log(`tartget:${this.target},data:${this.data}`)
})
}
在build里每个属性的键都会成为目标,除了指定的option以外
module.exports = grunt => {
//多目标模式,可以让任务根据配置形成多个子任务
grunt.initConfig({
build:{
//在option当中指定的信息会作为这个任务的配置选项出现,控制台打印不出options内容
options:{
foo:'bar'
},
css:'1',
js:'2'
}
})
//接收两个参数,第一个参数是任务的名字,第二个参数是一个函数(任务执行过程所需要做的事情)
grunt.registerMultiTask('build',function(){
console.log(this.options())//要想打印options里面的内容,需要用this的options方法
console.log(`tartget:${this.target},data:${this.data}`)
})
}
目标的属性值也可为options对象:
module.exports = grunt => {
//多目标模式,可以让任务根据配置形成多个子任务
grunt.initConfig({
build:{
//在option当中指定的信息会作为这个任务的配置选项出现
options:{
foo:'bar'
},
//如果说目标的配置也是一个对象的话,这个属性也可以添加options,这个options能覆盖掉对象里的options
css:{
options:{
foo:'baz'
}
},
js:'2'
}
})
//接收两个参数,第一个参数是任务的名字,第二个参数是一个函数(任务执行过程所需要做的事情)
grunt.registerMultiTask('build',function(){
console.log(this.options())
console.log(`tartget:${this.target},data:${this.data}`)
})
}
Grunt插件的使用
grunt.loadNpmTaks() 加载插件提供的一些任务:
grunt.loadNpmTasks('grunt-contrib-clean'),加载grunt-contrib-clean插件,这个插件用来清除在项目开发过程中产生的临时文件。
grunt插件常用命名方式:grunt-contrib-pluginname
使用grunt插件运行插件的任务时,完整插件名称后面的pluginname,其实就是提供的任务名称:yarn grunt pluginname //或者 npm run grunt pluginname
- grunt-contrib-clean:
安装: $ yarn add grunt-contrib-clean // 或者 npm install grunt-contrib-clean
module.exports = grunt => {
grunt.initConfig({
clean: {
temp: 'temp/app.js', // 所要清除的文件的具体路径
tempTxt: 'temp/*.txt', // 使用通配符*,删除所有txt文件
tempFiles: 'temp/**' // 使用**的形式,删除temp整个文件夹
}
})
grunt.loadNpmTasks('grunt-contrib-clean')
}
使用:插件中的任务需要在initConfig()中进行配置。
module.exports = grunt => {
grunt.initConfig({
clean:{
// temp:'temp/app.js'//清除temp下的app.js文件
// temp:'temp/*.txt'//清除temp下所有的txt文件
temp:'temp/**'//清除temp目录下所有的文件
}
})
grunt.loadNpmTasks('grunt-contrib-clean')
}
temp文件被清除
- Grunt-sass:是一个npm模块,在内部通过npm依赖sass,他需要一个npm提供sass模块进行支持,因此两个模块都需要安装:
安装插件模块:yarn add grunt-sass 或者 npm install grunt-sass sass --save-dev
配置sass:
module.exports = grunt => {
grunt.initConfig({
sass:{
main:{//需要指定输入文件,以及最终输出的css文件路径
files:{
//属性名(键)为,需要输出的css的路径
//属性值为需要输入的scss文件的路径
'dist/css/main.css':'src/scss/main.scss'
}
}
}
})
grunt.loadNpmTasks('grunt-sass')
}
此时运行控制台报错,需要传入一个implementation的选项,这是一个用来指定grunt-sass当中使用哪一个模块去处理sass的编译:
为sass添加option:
const sass = require('sass')//导入sass模块
module.exports = grunt => {
grunt.initConfig({
sass:{
options:{
sourceMap:true,//生成对应的sourceMap文件
implementation:sass//把sass模块传入属性当中
},
main:{//需要指定输入文件,以及最终输出的css文件路径
files:{
//属性名(键)为,需要输出的css的路径
//属性值为需要输入的scss文件的路径
'dist/css/main.css':'src/scss/main.scss'
}
}
}
})
grunt.loadNpmTasks('grunt-sass')
}
控制台命令运行:
可以在文件夹看到编译的scss文件:
更多的可以在grunt-sass官方文档看到。
- grunt-babel 插件,用来编译ES6语法,它需要使用 Babel 的核心模块 @babel/core,以及 Babel 的预设@babel/preset-env
安装插件模块:
yarn add grunt-babel @babel/core @babel/preset-env --dev
此时又需要grunt.loadNpmTasks()来添加babel任务,随着grunt-file越来越复杂,这个方法会用的越来越多,此时社区当中有一个模块(load-grunt-tasks)可以减少这个方法的使用:
- 安装模块:
yarn add load-grunt-tasks --dev //或者 npm install load-grunt-tasks -save-dev
- 基本使用:
const loadGruntTasks = require('load-grunt-tasks') module.exports = grunt => { loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务 }
安装babel模块后,修改对应的js:
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
grunt.initConfig({
babel: {//将ECMAScript最新特性转换为js
options: {
sourceMap: true,
// //你需要转换哪些特性,把这些特性打包形成了preset
presets: ['@babel/preset-env'] //env 会默认根据最新的es特性去做对应的转换
},
main: {
files: {
'dist/js/app.js': 'src/js/app.js'
}
}
}
})
loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务
}
运行yarn grunt babel
此时js文件夹里的js文件就是自动把原来写的es6的代码自动转为es5的方式,还生成了对应的sourceMap文件:
- grunt-contrib-watch插件,是指当文件发生改变时,可以实现自动跟踪编译
安装插件模块
yarn add grunt-contrib-watch --dev // 或者 npm install grunt-contrib-watch -save-dev
基本使用:
const sass = require('sass')//导入sass模块
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
grunt.initConfig({
sass:{
options:{
sourceMap:true,//生成对应的sourceMap文件
implementation:sass//把sass模块传入属性当中
},
main:{//需要指定输入文件,以及最终输出的css文件路径
files:{
//属性名(键)为,需要输出的css的路径
//属性值为需要输入的scss文件的路径
'dist/css/main.css':'src/scss/main.scss'
}
}
},
babel:{//将ECMAScript最新特性转换为js
options:{
sourceMap:true,
//你需要转换哪些特性,把这些特性打包形成了preset
presets:['@babel/preset-env']//env 会默认根据最新的es特性去做对应的转换
},
main:{
files:{
'dist/js/app.js':'src/js/app.js'
}
}
},
watch:{
js:{
files:['src/js/*.js']//监视特定文件
tasks:['babel']//当你监视的这些文件发生改变之后需要执行的任务
},
css:{
files:['src/scss/*.scss']//监视特定文件
tasks:['sass']//当你监视的这些文件发生改变之后需要执行的任务
},
}
})
// grunt.loadNpmTasks('grunt-sass')
loadGruntTasks(grunt)
//使用映射,确保在启动时,运行各种编译任务,然后再启动监听
grunt.registerTask('default',['sass','babel','watch'])
}
运行命令
yarn grunt