概要
一切的技术方案都是为了解决问题而生,工程化这个宽泛性的理念就是为了解决一些项目中实际的问题,如:
- 想要使用 ES6+ 新特性,但是兼容性有问题;(传统语言或语法的弊端)
- 想要使用 Less、Scss 增强 css 编程特性,但是环境不支持;(传统语言或语法的弊端)
- 想要使用模块化的方式提高项目可维护性和扩展性,但是环境不支持;(无法使用模块化、组件化)
- 部署上线前需要手动压代码及资源文件;(重复的机械式工作)
- 部署过程中需要手动上传代码到服务器;(重复的机械式工作)
- 多人合作开发,无法硬性要求大家的代码风格(代码风格统一,质量保证)
- 从仓库 pull 回来的代码质量无法保证(代码风格统一,质量保证)
- 部分功能开发前必须等待后端接口开发完成(依赖后端服务接口)
目录
- 工程化的表现
- 工程化的核心
- 理解脚手架
- Yeoman脚手架工具
- 了解Grunt
工程化的表现
一切以调高效率、降低成本、质量保证为目的的手段都属于工程化
,具体到项目的每个阶段,都有不同的提高效率的方式
- 创建项目:使用脚手架自动完成项目基础结构的搭建
- 编码:自动完成代码格式化、代码风格校验
- 预览 / 测试:热更新(Live Reloading、HMR)、使用 Mock 接口进行测试、Source Map 方便追踪源代码
- 提交:Git Hooks 进行项目提交前代码质量和代码风格的检查
- 部署:利用自动化发布工具轻松上线
工程化的核心
工程化的核心应是对项目整体的一种规划和架构,而工具只是帮助我们去落地、去实现这种规划或架构的一种手段。
大多数前端工程化的工具几乎都由 Node.js 开发,并且可以认为前端工程化是由 Node.js 强力驱动。
通过脚手架工具开发、自动化构建系统、模块化打包、项目代码规范化、自动化部署这几个方面落实前端工程化
理解脚手架
脚手架的本质作用
:创建项目基础的组织结构、提供给开发者一些项目规范和约定,针对一类相同的的项目,它们可能会有相同的组织结构、相同的开发范式、相同的模块依赖、相同的工具配置、相同的基础代码等,如果不依赖脚手架工具,由开发者逐个搭建,会做很多不必要的重复工作。如果利用脚手架工具快速搭建相同类型的项目骨架,帮助开发者省去一些准备工作,会很好的提高开发效率,为什么印刷行业会淘汰人工抄写行业者,道理一样
Yeoman脚手架工具
简介
:Yeoman 作为一款最老牌、功能最全面的脚手架工具,它有很多值得借鉴的项目创建哲学,不同于 Vue-Cli 只适用于 Vue 项目,yeoman的优势在于它可以搭配任何的 generator 创建任意类型的项目,而且我们可以通过建立属于我们自己的 generator(Generator实际上一个类,可以继承它并实现自己的子类) 创建属于自己的脚手架
yeoman的缺点与优势
:过于通用既是它的优势也是它的劣势,相对于 Vue-Cli 这种专注于 Vue-Cli 项目的脚手架来说,显然,Vue-Cli 创建的项目基础结构与规范约束更加具有实际价值
Yeoman的基本使用
1、在全局范围内安装 yo 模块
npm i yo -g // yo是yeoman的简写
2、安装对应的 generator
npm i generator-node -g // generator-node是为了方便创建node模块的脚手架generator,使用它可以为创建node模块搭建一个基础的项目结构
3、通过 yo 运行 generator
yo node // 这里的node是generator-node的后半部分
Note
:对于 yo 来说,不同的项目需要安装不同的 generator,例如为了创建 node 模块,可以安装 generator-node,并使用yarn node
创建一个基础的项目结构
如何使用Sub Generator
?
某些情况下,我们并不需要一个 Generator 全部的功能,这时我们可以通过选择性的使用属于这个 Generator 的部分 Sub Generator 功能,例如一个完整的 genertaor-node 由以下 Sub Generator 组成:
node:boilerplate
node:cli
node:editorconfig
node:eslint
node:git
node:readme
这时我们可以选择性的只使用其 cli 的功能:
yo node:cli // 使用语法很简单,原有基础上加上冒号及sub generator名称即可 // note:对于一个generator是否划分为多个sub generator,需要github阅读官方文档
使用yo的常规步骤
:
1、明确你的需求 2、根据需求找到合适的generator(在yo官网有generator查找功能) 3、全局范围安装generator 4、通过yo运行对应的generator(如generator-node,运行命令即是yo node) 5、通过命令行交互填写可选的配置选项(如webapp这种generator需要选取何种UI框架、是否启用eslint等) 6、生成你所需要的项目结构
自定义Generator
不同的 Generator 可以用来生成不同的项目,那也就是说,我们可以通过创建适用于自己项目的 Generator 来帮助我们快速生成自定义的项目结构与配置等。
即使市面上已经有很多的 generator,但是我们依然有创建属于自己 Generator 的必要,因为市面上的 generator 往往是通用的,但是我们在项目中往往会遇到一些项目基础代码甚至是业务代码也是重复的,这个时候,这些重复的基础代码和业务代码都属于公共部分,为了避免重复劳动,它们都可以被纳入脚手架工具中去生成。
这时,我们就需要自定义属于我们公司项目自己的 generator。例如资金管理系统,绝大多数功能都是通用的,但是每个不同客户的资金管理系统又拥有属于自己的定制化功能,因此建立一套必要的脚手架工具可以方便开发。
有些公司可能会基于 git 分支去解决这种重复劳动的问题,将一个基础项目建立在一个单独的分支上,每次有新的项目时,从这个分支上拉取一个新的分支来开发,这也是一种方式,尤其适用于那种前后端不分离的项目。
但是单独对前端来说,使用自定义的脚手架可能是一种更加规范的行为
使用Yeoman自定义脚手架(下文统称为generator)
1.1、概念
:Yeoman 提供了一个基类用于帮助开发者建立属于自己特定的脚手架工具。围绕着这个基类做开发的过程即脚手架的创建过程,下文的 generator 即指开发者自定义的脚手架。
1.2、特殊的目录结构
:如果希望使用 Yeoman 建立属于自己的脚手架,那么脚手架目录必须遵循 Yeoman 的规定,具体如下:
// 项目根目录 |---- package.json |---- generators/ |---- app/ |---- index.js |---- router/ |---- index.js
1.3、特殊的 generator 名称
:通过 Yeoman 建立的自定义脚手架,其名称必须是以下结构:
generator-<name> // 如我希望建立一个名为saltire的脚手架,则generator名称为generator-saltire
1.4、创建生成器目录
:创建生成器目录并完成 npm 初始化
mkdir generator-saltire cd generator-saltire npm init
1.5、安装yeoman-generator
:yeoman-generator 模块提供了生成器的基类,这个基类中提供了很多工具函数让我们在创建生成器时更加便捷
npm i yeoman-generator
1.6、按照目录规则创建项目目录及文件
1.7、修改 app 目录下的 index.js
:该文件是 generator 的核心入口,它需要导出一个继承自 Yeoman Generator 的类,在此类中,可以通过调用父类提供的一些工具函数来实现自定义脚手架的功能
const Generator = require('yeoman-generator') module.exports = class extends Generator { // yeoman在生成文件阶段会自动调用此方法 // 该方法中,可以通过读写文件的方式往生成的目录中写入一些文件 writing() { // 这里的fs模块与Node的fs模块不同,相较于原生的fs模块,它封装的更加强大 // fs.write方法接收两个参数,一个是写入文件的绝对路径,另一个是写入的内容 this.fs.write( this.destinationPath('test.txt'), '这是测试内容' ) } }
1.8、使用 npm link 将该generator链接到全局范围
:
npm link
1.9、使用自定义生成器generator-saltire
:
mkdir my-project cd my-project yo saltire // 这里和使用yeoman模块的规则一致,只需generator后的名称
自定义 Yeoman 脚手架时使用模板文件
假设我们已遵循上面1.6的步骤创建好文件结构,现在我们希望建立脚手架时能够将已经存在的文件作为模板来生成某些内容,于是我们在项目的 app 文件夹再建立一个 templates 文件夹,并初始化一个文件 foo.txt,如下:
|---- package.json |---- generators/ |---- app/ |---- templates/ |---- foo.txt |---- index.js |---- router/ |---- index.js
模板文件内部使用 ejs 引擎渲染,因此模板文件内部可以使用任意的 ejs 语法:
这是一个模板文件 内部可以使用 ejs 模板标记输出数据 例如:<%= title %> <% if ( success ) { %> 哈哈哈,成功 <% } %>
模板文件的使用依赖于 fs.copyTpl 方法:
const Generator = require('yeoman-generator') module.exports = class extends Generator { // yeoman在生成文件阶段会自动调用此方法 // 该方法中,可以通过读写文件的方式往生成的目录中写入一些文件 writing() { // 获取模板文件路径 const tpl = this.templatePath('foo.txt') // 输出目标路径 const outPath = this.destinationPath('test.txt') // 模板数据上下文 const context = { title: 'Hello', success: false } this.fs.copyTpl(tpl, outPath, context) } }
相对于手动调用 fs.write 创建文件,模板的方式大大提高了效率,特别是在文件比较多比较复杂的情况下
自定义 Yeoman 脚手架时使用 prompt
脚手架会根据用户的需要定制某些内容,这就不可避免需要用户输入某些参数,Yeoman 使用了 inquirer.js 来获取用户的输入
const Generator = require('yeoman-generator') module.exports = class extends Generator { // yeoman在生成文件阶段会自动调用此方法 // 该方法中,可以通过读写文件的方式往生成的目录中写入一些文件 writing() { // 获取模板文件路径 const tpl = this.templatePath('foo.txt') // 输出目标路径 const outPath = this.destinationPath('test.txt') // 模板数据上下文 // this.answers来自问询阶段的用户输入 const context = this.answers this.fs.copyTpl(tpl, outPath, context) } // yeoman在询问用户阶段会自动调用此方法 prompting() { return this.prompt( [ { type: 'input', name: 'name', message: 'your project name', default: 'test' }, { type: 'input', name: 'title', message: 'your project title', default: 'hhh' }, { type: 'input', name: 'success', message: 'your project success', default: false } ] ).then(answers => { // 这个数据便可以作为模板数据的上下文使用 this.answers = answers }) } } // 生成结果 这是一个模板文件 内部可以使用 ejs 模板标记输出数据 例如:hhh 哈哈哈,成功
基于已存在的 Vue 项目创建脚手架
和上面的根据模板文件创建脚手架一致,这里需要在 app 文件夹下新建 templates 文件夹,并将 Vue 项目中的所有文件全部拷贝到该目录下
const Generator = require('yeoman-generator') module.exports = class extends Generator { // yeoman在生成文件阶段会自动调用此方法 // 该方法中,可以通过读写文件的方式往生成的目录中写入一些文件 writing() { // 这里需要给出所有的templates文件夹下的文件路径 const templates = [ '.browserslistrc', '.editorconfig', '.env.development', '.env.production', 'src/main.js', 'public/index.html', 'src/APP.vue', 'src/util/https.js', 'src/util/cnzz.js', ] // 遍历templates文件夹下的所有文件,使用fs.copyTpl方法复制文件 this.templates.forEach(item => { // 获取模板文件路径 const tpl = this.templatePath(item) // 输出目标路径 const outPath = this.destinationPath(item) const context = this.answers this.fs.copyTpl(tpl, outPath, context) }) } // yeoman在询问用户阶段会自动调用此方法 prompting() { return this.prompt( [ { type: 'input', name: 'name', message: 'your project name', default: 'test' }, ] ).then(answers => { // 这个数据便可以作为模板数据的上下文使用 this.answers = answers }) } }
利用 Node.js 的 fs 模块遍历文件夹中的所有文件
以下代码将指定文件夹中的所有文件的相对路径输出至一个数组:
var fs = require('fs'); var path = require('path'); function readDirectory(pathName, dirs = []) { //异步读取文件夹 fs.readdirSync(pathName).forEach((item, index) => { //异步处理路径 let stat = fs.lstatSync(path.join(pathName, item)) if (stat.isDirectory()) { readDirectory(path.join(pathName, item), dirs) } else if (stat.isFile()) { dirs.push(path.join(pathName, item)) } }) return dirs } let filePaths = readDirectory('/Users/edz/projectNew/ownTest/Blog/yemo-demo/generator-knight/generators/app/templates') filePaths = filePaths.map(item => { return item.split('templates/')[1] }) fs.writeFile('temp.js', filePaths, function (err) { if (err) { console.log(err); return false; } console.log("写入成功") })
Grunt 、Gulp 以及 FIS 的对比
简介
:npm scripts 确实能解决一部分的自动化构建任务,但是对于相对复杂的构建过程,npm 的 scripts 就显得非常吃力。这时,我们需要更为专业的构建工具,如:Grunt 、Gulp 以及 FIS,严格来说,Webpack 是一个模块打包工具,因此,这里暂时不考虑它
这些工具都可以帮你结局那些重复无聊的任务,从而实现自动化。用法上,它们都大体相同,都是先通过一些简单的代码去组织一些插件的使用,然后便可以使用这些工具去执行各类重复的工作
Grunt 可以说是前端最早的构建系统,它的插件生态非常丰富,这些多样的插件几乎可以帮你做任何想做的事。但是,由于它的工作过程是基于临时文件去实现的,所以说它的构建速度相对较慢
例如,我们利用 Grunt 去实现项目中 sass 文件的构建,我们一般会先对 sass 文件做编译操作,再去添加一些私有属性的前缀,最后再去压缩代码,那么在这样一个过程中,Grunt 每一步都会有磁盘读写操作,比如,sass 编译过后,它会将编译结果写入到一个临时的文件,下一个插件会去读取这个临时文件并添加前缀
这样一来,你处理的环节越多,临时文件被读写的次数就越多,对于超大型项目,项目文件时非常庞大的,使用 Grunt 构建速度就会非常慢
Gulp 解决了 Grunt 构建速度慢的问题,因为它是基于内存实现的,也就是说,它对文件的处理操作都是在内存中完成的,相对于磁盘来说,内存的速度远大于磁盘。此外,Gulp 支持同时执行多个任务,效率也大大提高,而且,相对于 Grunt,它的使用方式更加直观易懂,插件生态也同样非常完善,所以说,它后来居上,目前更受欢迎,应该算是目前市面上最流行的前端构建系统了
FIS 则是百度的前端团队开源的一款构建系统,相对于前两个构建系统来说,FIS 更像是一种捆绑套餐,它把我们在项目中一些常见的需求尽可能的集成在了系统内部,例如,在 FIS 中可以很轻松的实现资源加载、模块化开发、代码部署,正是因为这种大而全并且集成常用需求的思想,它在国内较受欢迎
总体来说, FIS 更受初学者的喜爱,但是,如果你的需求灵活多变,Gulp 和 Grunt 才是更好的选择
Grunt 的基本使用
先打开一个空文件夹,并使用 yarn init 进行初始化,紧接着:
安装grunt
:
yarn add grunt
添加入口文件
:在项目根目录添加 gruntfile.js,该文件将作为整个 Grunt 的入口,用于定义一些需要 Grunt 自动执行的任务
// 该文件需要导出一个函数 // 此函数接收一个叫 grunt 的参数,内部定义了一些创建任务时用到的 API module.exports = grunt => { // registerTask 是用于创建任务的API,任务被创建后,就可以在终端中使用yarn grunt foo(任务名称)执行这个任务 grunt.registerTask('foo', () => { console.log('创建任务的API') }) grunt.registerTask('bar', () => { console.log('我是第二个任务') }) // 对于任务名称为default的任务,在终端中执行时只需输入 yarn grunt 即可,grunt 会自动调用default这个task grunt.registerTask('default', () => { console.log('我是默认任务') }) // 通常来说,default被用于映射依次执行多个任务 // 如下,regeisterTask第二个参数设为数组,执行default时,便会去依次执行数组中的任务,相当于是通过default这一个任务将foo和bar串联到了一起 grunt.registerTask('default', ['foo', 'bar']) // grunt 默认只支持同步代码,所以以下代码时不可行的 grunt.registerTask('async-task-error', () => { setTimeout(() => { console.log('async task working!') }, 1000) }) // 如果希望在task中执行异步任务,需要手动执行Grunt提供的一个async函数 grunt.registerTask('async-task', function() { // 注意,这里不能使用箭头函数了 const done = this.async() setTimeout(() => { console.log('async task working') //done函数的执行标志异步任务的结束 done() }, 1000) }) }
标记任务失败
:如何指定在发生某些情况时指定任务失败呢?对于同步任务,只需在任务的回调函数中返回 false,而对于异步任务,则是在执行 done 函数时传入 false
module.exports = grunt => { grunt.registerTask('foo', () => { console.log('创建任务的API') // 返回false则标志着foo任务时一个失败的任务,grunt会报出这个错误 return false }) grunt.registerTask('bar', () => { console.log('我是第二个任务') }) // 当串联任务中的某个任务失败后,后面的任务就不会执行了,如foo失败,bar就不会被执行 grunt.registerTask('default', ['foo', 'bar']) // 想指定一个异步任务失败时,done函数调用时传入false grunt.registerTask('async-task', function() { // 注意,这里不能使用箭头函数了 const done = this.async() setTimeout(() => { console.log('async task working') //done函数的执行标志异步任务的结束,传入false表明该异步任务失败了 done(false) }, 1000) }) }
Grunt 配置选项 API
除了 registerTask 方法,Grunt 还提供了一个用于去添加配置选项的 API,叫做 initConfig,例如我们使用 Grunt 为我们项目压缩文件时,我们就可以通过这个 API 去配置我们需要压缩的文件的路径
module.exports = grunt => { // 这个API接收一个对象形式的参数,该对象的键通常与我们使用registerTask创建的任务的任务名称一致 grunt.initConfig({ // 属性值可以是任意类型数据,例如,这里是对象 foo: { bar: 1 } }) // 有了配置选项后,便可以在任务中使用这个配置 grunt.registerTask('foo', () => { console.log(grunt.config('foo').bar) }) }
Grunt 多目标任务
除了普通的任务形式以外,Grunt 还支持一种叫做多目标形式的任务,可以理解为子任务的概念,这种形式的任务在后续实现项目中各类构建任务时非常有用,多目标任务使用 registerMultiTask 定义
module.exports = grunt => { grunt.initConfig({ // 配置多任务的具体目标时,必须是一个对象 // 如下,我们为build任务创建了两个目标,一个叫做css,一个叫做js // 执行build时,grunt会同时执行css和js这两个目标,也就相当于以两个子任务的形式去运行 // 如果只希望运行build任务中的其中一个目标,比如js,那么可以使用:yarn grunt build:js // 在build对象中指定的任意键都会成为build任务中的一个目标,如这里的css和js,不过,有一个键除外,它叫做options,options对象会被作为任务的配置选项,可以在任务中通过this.options()方法获取 build: { css: 'x', js: 'y', options: { name: 'test' } } }) // 多目标任务,可以让任务根据配置形成多个子任务(子任务官方称之为目标) // 创建多目标任务时,必须为这种任务创建多个不同的目标,如果不配置便直接运行该任务就会报错,具体配置方法则是使用initConfig方法 grunt.registerMultiTask('build', function () { console.log('build task') // 在build任务中,我们可以通过this来获取目标的相关信息 console.log(`target: ${this.target},data: ${this.data}`) // 通过this.options方法获取该任务的配置选项 const options = this.options() console.log(`options: ${options}`) }) // 我们也可以为目标指定相应的配置选项 // grunt.initConfig({ // build: { // css: 'x', // js: { // // 目标的配置会覆盖掉build任务的options选项 // options: { // name: '目标的name会覆盖掉test' // } // }, // options: { // name: 'test' // } // } // }) }
Grunt 插件
插件机制是 Grunt 的核心,插件之所以存在道理也很简单,因为很多构建任务都是通用的,例如你想给你的项目压缩代码从而减少项目体积,别人的项目也会有这个需求,所以说,社区中就这样出现了很多这样预设的插件。
这些插件内部封装了一些通用的构建任务,一般情况下,我们的构建过程就是由这些通用的构建任务组成的,使用这些插件的过程也很简单,基本也就是先通过 npm 安装这些插件,再在 gruntfile.js 当中载入这些插件提供的任务,最后,我们根据这些插件的文档去设置配置选项
下面介绍一款插件:grunt-contrib-clean,它用于自动清除项目在开发过程中产生的一些临时文件,首先,通过 yarn add grunt-contrib-clean 在项目中安装这个插件,之后通过修改 gruntfile 来加载这个插件的构建任务:
module.exports = grunt => { grunt.initConfig({ clean: { // 这里通过temp选项指定要清除的文件路径 temp: 'temp/app.js' // 除了指定完整的路径,也可以使用通配符,例如:删除temp目录下所有的txt文件 // temp: 'temp/*.txt' // 如果想清空temp文件夹下所有内容,可以使用两个* // temp: 'temp/**' } }) // 绝大多数情况下,插件的命名规范都是grunt-contrib-后面跟着任务名称,如这个插件clean即是任务名称,因此,我们可以通过在终端中执行 yarn grunt clean来执行这个插件提供的clean任务 // 但是,大多数情况下,这类任务通常都是上面所说的多目标任务,如果你不配置相应的目标,直接运行时会报错的,至于怎么配置,就要看插件的文档了 grunt.loadNpmTasks('grunt-contrib-clean') }
总结
:如果想使用 Grunt 插件,应先在 npm 上找到相应的 npm 模块,安装到项目中后,在 gruntfile.js 配置文件中配置该插件,具体配置规则需要去翻阅该插件的官方文档
Grunt 常用的构建任务
grunt-sass
:grunt-sass 插件用于帮助我们完成项目中 sass 相关的构建,那么如何使用 grunt-sass 呢:
1、 安装依赖:
yarn add grunt-sass sass -D
2、修改 gruntfile.js
const sass = require('sass') module.exports = grunt => { grunt.initConfig({ sass: { options: { // 关于options的配置选项,建议根据需求通过官方文档查阅 implementation: sass }, // 指定一个main目标,在这个目标中指定sass文件路径及输出的css 文件路径 main: { files: { // 键为输出路径,值为输入路径 'dist/css/main.css': 'src/scss/main.scss' } } } }) // grunt-sass 同样也是一个多目标的任务,因此也需要在initConfig中为其配置一些目标 grunt.loadNpmTasks('grunt-sass') }
grunt-babel
:前端项目中编译 ES6 语法
1、 安装依赖:
yarn add grunt-babel @babel/core @babel/preset-env -D
2、修改 gruntfile.js
随着使用的插件越来越多,使用 loadNpmTasks 加载插件也会越来愈多,社区中有一个模块叫做 load-grunt-tasks 可以帮助我们减少 loadNpmTasks 的频繁调用,当然使用之前也要通过 yarn add load-grunt-tasks -D
安装
const sass = require('sass') const loadGruntTasks = require('load-grunt-tasks') module.exports = grunt => { grunt.initConfig({ sass: { options: { // 关于options的配置选项,建议根据需求通过官方文档查阅 implementation: sass }, // 指定一个main目标,在这个目标中指定sass文件路径及输出的css 文件路径 main: { files: { // 键为输出路径,值为输入路径 'dist/css/main.css': 'src/scss/main.scss' } } }, babel: { options: { // 指定编译哪些ECMAScript特性 presets: ['@babel/preset-env'] }, main: { files: { 'dist/js/app.js': 'src/js/app.js' } } } }) // grunt-sass 同样也是一个多目标的任务,因此也需要在initConfig中为其配置一些目标 // grunt.loadNpmTasks('grunt-babel') loadGruntTasks(grunt) // load-grunt-tasks会自动加载所有的grunt插件 }
grunt-contrib-watch
:项目中文件内容改变后,自动重新构建项目
1、 安装依赖:
yarn add grunt-contrib-watch -D
2、修改 gruntfile.js
const sass = require('sass') const loadGruntTasks = require('load-grunt-tasks') module.exports = grunt => { grunt.initConfig({ sass: { options: { // 关于options的配置选项,建议根据需求通过官方文档查阅 implementation: sass }, // 指定一个main目标,在这个目标中指定sass文件路径及输出的css 文件路径 main: { files: { // 键为输出路径,值为输入路径 'dist/css/main.css': 'src/scss/main.scss' } } }, babel: { options: { // 指定编译哪些ECMAScript特性 presets: ['@babel/preset-env'] }, main: { files: { 'dist/js/app.js': 'src/js/app.js' } } }, watch: { js: { // 这里只指定了一个文件,实际使用时会使用通配符指定js文件夹下所有文件 files: ['src/js/app.js'], // 指定'src/js/app.js'内容变化后,执行哪些任务 tasks: ['bable'] }, css: { files: ['src/scss/*.scss'], tasks: ['sass'] } } }) // 为了保证先执行sass和babel任务再执行watch任务对文件进行监听,通常会做一个映射如下: // 这样,只需在终端中执行 yarn grunt 即可 grunt.registerTask('default', ['sass', 'babel', 'watch']) // grunt-sass 同样也是一个多目标的任务,因此也需要在initConfig中为其配置一些目标 // grunt.loadNpmTasks('grunt-babel') loadGruntTasks(grunt) // load-grunt-tasks会自动加载所有的grunt插件 }
总结
:这三个插件可说是使用 Grunt 时最常用的三个插件,但是除此之外,Grunt 还有更多丰富的插件,但是由于 Grunt 基本已经退出历史舞台了,因此,只需通过上面内容了解其工程化的思路即可,并且,了解以上配置后,当我们拿到一个旧项目并且其使用 Grunt 时,也有利于我们分析该项目工程化的思路并根据产品需求做出修改
关于 Grunt 结合 browser-async 以及 npm-run-all 自动编译项目并且监视文件变化且自动在浏览器中打开的实例可以在项目实例下的 grunt-plugins.zip 中找到,当然使用这个项目前需要提前全局安装好 browser-async