工程化概述

全服武装:通过过工程化提升【战斗力】

工程化的定义和主要解决的问题

前端工程化是使用软件工程的技术和方法来进行前端的开发流程、技术、工具、经验等规范化、标准化。
主要解决的问题

  • 传统语言或语法的弊端
  • 无法使用模块化/组件化
  • 重复的机械式工作
  • 代码风格统一、质量保证
  • 依赖后端服务接口支持
  • 整体依赖后端项目

一个项目过程中工程化的表现

一些以提高效率,降低成本,质量保证为目的的手段都属于【工程化】
一些重复的工作都应该被自动化

工程化不等于工具

工具不是工程化的核心,工程化的核心是对整体项目的规划或者架构,工具是落地或者实现的手段,
项目开发:
image.png

工程化与Node.js

居功至伟的node,前端工程化是由node强力驱动的

脚手架工具

前端工程化的发起者

脚手架工具概要

脚手架的本质作用:

创建项目基础结构,提供项目规范和约定

  • 相同的组织结构
  • 相同的开发范式
  • 相同的模块依赖
  • 相同的工具配置
  • 相同的基础代码

前端脚手架

前端基础选型比较多样,并且没有统一的标准,以独立的工具存在,并且相对复杂

常用的脚手架工具

根据信息创建对应的项目基础结构,一般这只适用于自身服务的框架
创建一个组件/模块所需要的文件,脚手架会创建一种更为稳健的创建方式

Yeoman

  • yeoman更像是一个脚手架的运行平台
  • 根据yeoman搭配不同的generator去创建任何类型的项目
  • 创建自己的generator可以去创建属于自己的前端脚手架工具

优点也是缺点

  • 过于通用,不够专注

基础使用

  • 在全局范围安装yo

    1. npm install yo --global
    2. yarn global add yo
  • 安装对应的generator

    1. npm install generator-node --global
    2. yarn global add generator-node
  • 通过yo运行generator

    1. cd path/to/project-dir
    2. mkdir my-module
    3. yo node

    Sub Generator

  • 项目转换为cli应用

    1. yo node:cli
  • 安装依赖项

    1. yarn
  • 链接到全局范围

    此处有坑,如果和其他文件名冲突,需要yarn unlink,再转npm,用npm link

  1. yarn link
  • 通过项目名运行,判断项目是否可以运行

    1. object --help

    使用步骤总结

  • 明确你的需求;

  • 找到合适的Generator;
  • 全局范围安装找到的Generator;
  • 通过Yo运行对应的Generator;
  • 通过命令行交互填写选项;
  • 生成你所需要的项目结构;

自定义Generator

用来生成公共部分代码
创建Generator本质上就是一个npm模块image.png
命名规则

  1. generator-<name>

创建Generator

  1. // 创建项目文件夹
  2. mkdir generator-demo1
  3. 进入文件夹
  4. cd generator-demo1
  5. // 引入yeoman-generator库
  6. yarn add yeoman-generator
  7. // 创建generator的核心入口
  8. touch generators/app/index.js
  9. // 链接全局
  10. yarn link //或者 npm link
  11. // 创建生成目录文件夹
  12. mkdir demo-temp
  13. // 进入文件夹
  14. cd you-temp
  15. 在目标文件夹下,运行yo命令
  16. yo you-temp
  1. // generators/app/index.js
  2. // 此文件作为Generator的核心入口
  3. // 需要导出一个继承自Yeoman Generator的类型
  4. // Yeoman Generator在工作是会自动调用我们在此类型中定义的一些生命周期方法
  5. // 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入
  6. const Generator = require('yeoman-generator')
  7. module.exports = class extends Generator {
  8. writing () {
  9. // yeoman 自动在生成文件阶段调用次方法
  10. // 我们这里尝试往项目目录中写入文件
  11. this.fs.write(
  12. this.destinationPath('temp.txt'),
  13. Math.random().toString()
  14. )
  15. }
  16. }

根据模板创建文件

  1. const Generator = require('yeoman-generator')
  2. module.exports = class extends Generator {
  3. writing () {
  4. // 通过模板方式写入文件到目标目录
  5. // 模板文件路径
  6. const tmpl = this.templatePath('foo.txt')
  7. // 输出目标路径
  8. const output = this.destinationPath('foo.txt')
  9. // 模板数据上下文
  10. const context = { title: 'hello yeoman!', success: true }
  11. this.fs.copyTpl(tmpl, output, context)
  12. }
  13. }
  1. // generators/app/templates/foo.txt
  2. 这是一个模板文件
  3. 内部可以使用EJS 模板标记输出数据
  4. 例如<%= title %>
  5. 其他的EJS 语法也支持
  6. <% if (success) { %>
  7. 哈哈哈
  8. <% }%>

接收用户输入

  1. const Generator = require('yeoman-generator')
  2. module.exports = class extends Generator {
  3. prompting() {
  4. // Yeomen 在询问用户环节会自动调用此方法
  5. // 在此方法中可以调用父类的prompt() 方法会发出对用户的命令行询问
  6. return this.prompt([
  7. {
  8. type: 'input',
  9. name: 'title',
  10. message: 'you project name?',
  11. default: this.appname //appname 为项目生成目录名称
  12. }
  13. ]).then((answers) => {
  14. // answers => {name:'user input value}
  15. this.answers = answers
  16. })
  17. }
  18. writing() {
  19. // 通过模板方式写入文件到目标目录
  20. // 模板文件路径
  21. const tmpl = this.templatePath('demotemp.html')
  22. // 输出目标路径
  23. const output = this.destinationPath('demo.html')
  24. // 模板数据上下文
  25. const context = this.answers
  26. this.fs.copyTpl(tmpl, output, context)
  27. }
  28. }
  1. // generators/app/templates/demotemp.html
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title><%= title %></title>
  8. </head>
  9. <body>
  10. <%= title %>
  11. </body>
  12. </html>

案例

  1. const Generator = require('yeoman-generator')
  2. module.exports = class extends Generator {
  3. prompting() {
  4. // Yeomen 在询问用户环节会自动调用此方法
  5. // 在此方法中可以调用父类的prompt() 方法会发出对用户的命令行询问
  6. return this.prompt([
  7. {
  8. type: 'input',
  9. name: 'title',
  10. message: 'you project name?',
  11. default: this.appname //appname 为项目生成目录名称
  12. },
  13. {
  14. type: 'input',
  15. name: 'success',
  16. message: 'you success is?',
  17. default: this.appname //appname 为项目生成目录名称
  18. }
  19. ]).then((answers) => {
  20. // answers => {name:'user input value}
  21. this.answers = answers
  22. })
  23. }
  24. writing() {
  25. // 通过模板方式写入文件到目标目录
  26. let templateList = [
  27. 'bar.html',
  28. 'foo.txt'
  29. ]
  30. templateList.forEach(item => {
  31. this.fs.copyTpl(
  32. this.templatePath(item),
  33. this.destinationPath(item),
  34. this.answers
  35. )
  36. })
  37. }
  38. }

发布Generator

其实就是发布npm模块

  • 托管到一个公开的源代码仓库上,例如:Github
  • 发布模块,命令如下:
    1. npm publish // 或者 yarn publish

Plop

使用

  1. // 1.安装plop模块
  2. yarn add -D plop
  3. // 2.根目录下创建plopfile.js文件
  4. // plopfile.js
  5. // plop入口文件,需要导出一个函数
  6. // 次函数接收一个plop对象,用于创建生成器任务
  7. module.exports = (plop) => {
  8. plop.setGenerator('demoComponent', {
  9. description: 'create a demoComponent',
  10. prompts: [
  11. {
  12. type: 'input',
  13. name: 'title',
  14. message: 'demoComponent name',
  15. default: 'Mycomponent'
  16. }
  17. ],
  18. actions: [
  19. {
  20. type: 'add', //代表添加文件
  21. path: 'src/components/{{title}}/{{title}}.js', //指prompts中name的值
  22. templateFile:'plop-templates/component.hbs'
  23. },
  24. {
  25. type: 'add', //代表添加文件
  26. path: 'src/components/{{title}}/{{title}}.css', //指prompts中name的值
  27. templateFile:'plop-templates/component.css.hbs'
  28. },
  29. ]
  30. })
  31. }
  32. // plop-templates 模板文件夹
  33. // component.hbs //模板js文件
  34. // component.css.hbs //模板css文件
  35. // 3.运行
  36. // yarn plop demoComponent //创建的文件名

总结:

  • 将plop模块作为项目开发依赖安装
  • 在项目根目录下创建一个 plopfile.js 文件
  • 在plopfile.js文件中定义脚手架任务
  • 编写用于生成特定类型文件的模板
  • 通过Plop提供的CLI运行脚手架任务

脚手架的工作原理

脚手架工具实际就是一个node:cli 应用

  1. // 初始化项目
  2. yarn init --yes
  3. // 初始化文件中添加cli的入口bin字段
  4. // package.json
  5. {
  6. ...,
  7. "bin":cli.js
  8. }
  9. // cli.js
  10. #!/usr/bin / env node
  11. // NODE CLI应用入口文件必须要这样的头
  12. // 如果是Linux或者macOS系统下还需要修改此文件的读写权限为755
  13. // 具体就是通过 chmod 755 cli.js 实现修改
  14. console.log('is working……')
  15. // 运行命令验证是否可以正常运行
  16. sample-scaffolding
  1. yarn add inquirer
  2. // cli.js文件
  3. const path = require('path')
  4. const inquirer = require('inquirer')
  5. const ejs = require('ejs')
  6. inquirer
  7. // 提问
  8. .prompt([
  9. {
  10. type: 'input',
  11. name: 'name',
  12. message: 'Write the Project name?'
  13. },
  14. {
  15. type: 'input',
  16. name: 'title',
  17. message: 'Write the Project-file title?'
  18. }
  19. ])
  20. .then((answers) => {
  21. // 根据用户回答的内容生成文件
  22. // 模板目录
  23. // 根目录下新建templates模板文件夹,文件夹下新建项目模板文件
  24. const tmplDir = path.join(__dirname, 'templates')
  25. // 目标目录
  26. const destDie = process.cwd()
  27. // 将模板下的文件全部转换输出到目标目录
  28. fs.readdir(tmplDir, (err, files) => {
  29. if (err) throw err
  30. files.forEach((file) => {
  31. // 通过模板引擎渲染文件
  32. ejs.renderFile(path.join(tmplDir, file), answers, (err, result) => {
  33. if (err) throw err
  34. // 将结果写入目标文件路径
  35. fs.writeFileSync(path.join(destDie, file), result)
  36. })
  37. })
  38. })
  39. })

脚手架的工作原理

  • 通过命令行交互询问用户问题
  • 根据用户回答的结果生成文件

自动化构建

自动化构建简介

就是自动化构架工作流

作用:脱离运行环境兼容带来的问题,在开发阶段,使用提高效率的语法,规范和标准

在开发网页应用时使用ECMAScript Next,Sass,模板引擎,这些用法大都不被浏览器直接支持,这个时候自动化构建工具就排上用场了,构建转换那些不被支持的【特性】

自动化构建初体验

……

常用的自动化构建工具

  • Grunt:磁盘读写
  • Gulp:在内存中完成,可同时执行多个任务
  • FIS:百度支持,微内核

    Gunt

    基本使用

    ```javascript // 初始化项目 yarn init —yes // 添加grunt库 yarn add grunt //根目录下创建grunt入口文件 gruntfile.js

// gruntfile.js // 用于定义一些需要grunt自动执行的任务 // 需要导出一个函数 // 次函数接收一个grunt的形参,内部提供一些创建任务时可以用到的API module.exports = (grunt) => { // yarn grunt foo grunt.registerTask(‘foo’, () => { console.log(111) })

  1. // yarn grunt bar
  2. grunt.registerTask('bar', () => {
  3. console.log(222)
  4. })
  5. // yarn grunt
  6. // grunt.registerTask('default', () => {
  7. // console.log('default')
  8. // })
  9. // yarn grunt 同时执行foo,bar
  10. grunt.registerTask('default', [ 'foo', 'bar' ])

// yarn grunt async-task // 默认支持同步模式,异步需要添加this.async(),因为箭头函数无this,所以用function grunt.registerTask(‘async-task’, function() { const done = this.async() setTimeout(() => { console.log(‘async-task’) done() // 表示任务已经完成 }, 1000) }) }

  1. <a name="cOLnv"></a>
  2. #### 标记任务失败
  3. - 在任务中return false
  4. - 命令中添加 --force
  5. - 异步任务中给done添加实参
  6. ```javascript
  7. grunt.registerTask('foo', () => {
  8. console.log(111)
  9. return false
  10. })
  11. yarn grunt --force
  12. grunt.registerTask('async-task', function() {
  13. const done = this.async()
  14. setTimeout(() => {
  15. console.log('async-task')
  16. done(false) // 添加实参
  17. }, 1000)
  18. })

配置方法

module.exports = (grunt) => {
    grunt.initConfig({
        foo: {
            bar: 123
        }
    })

    grunt.registerTask('foo', () => {
        grunt.config('foo.bar')
    })
}

多目标任务

module.exports = (grunt) => {
    grunt.initConfig({
        build: {
            //options作为任务的配置的选项,不参与打印
            options: {
                foo: 'bar'
            },
            css: '1',
            js: '2'
        }
    })
    // 多目标模式,可以让那个任务根据配置形成多个子任务
    grunt.registerMultiTask('build', function() {
        console.log('build task')
        console.log(this.options())
        console.log(`target:${this.target},data:${this.data}`)
    })
}

// 运行子任务
// yarn grunt build:css

插件的使用

插件是grunt的核心

  • 安装插件
  • 加载插件
  • 在任务中添加配置选项

    常用插件及总结

    ……

Gulp

基本使用

  • 安装gulp
  • 创建gulpfile.js ```javascript // yarn gulp foo exports.foo = (done) => { console.log(‘111’) done() //任务执行完成 }

// yarn gulp exports.default = (done) => { console.log(‘default task’) done() }

// 4.0以前版本 // yarn gulp bar const gulp = require(‘gulp’) gulp.task(‘bar’, (done) => { console.log(‘bar’) done() })

<a name="4JlVZ"></a>
#### 组合任务
```javascript
const { series, parallel } = require('gulp')

const task1 = (done) => {
    setTimeout(() => {
        console.log('task1')
        done()
    }, 1000)
}
const task2 = (done) => {
    setTimeout(() => {
        console.log('task2')
        done()
    }, 1000)
}
const task3 = (done) => {
    setTimeout(() => {
        console.log('task3')
        done()
    }, 1000)
}

// 串行任务
// exports.default = series(task1, task2, task3)

// 并行任务
// exports.default = parallel(task1, task2, task3)

异步任务

处理异步任务解决方法

  • 回调方式解决
  • promise的回调方案解决
  • node 环境是8以上的版本可以使用async await
  • stream是最常见的使用方式 ```javascript const fs = require(‘fs’)

// yarn gulp callback exports.callback = (done) => { console.log(‘callback task’) done() } // 与node当中的回调标准相同,都是错误优先的回调标准 exports.callback_error = (done) => { console.log(‘callback task bad’) done(new Error(‘bad’)) }

// yarn gulp promise exports.promise = () => { console.log(‘promise task’) return Promise.resolve() } exports.promise_error = () => { console.log(‘promise task bad’) return Promise.reject(new Error(‘bad’)) }

// yarn gulp async const timeout = (time) => { return new Promise((resolve) => { setTimeout(resolve, time) }) }

exports.async = async () => { await timeout(100) console.log(‘async task’) }

// yarn gulp stream exports.stream = () => { const readStream = fs.createReadStream(‘package.json’) const writeStream = fs.createWriteStream(‘temp.txt’) readStream.pipe(writeStream) return readStream } ```

构建过程核心工作原理

image.png
image.png

文件操作API

……

案例

封装工作流

准备

image.png

提取gulpfile

简单步骤讲解:

  • 创建新项目文件夹,项目根目录下创建lib文件夹,内部创建index.js
  • 新项目内index.js内复制原来的gulpfile.js中的内容
  • 新项目内复制原来项目内的devDependencies至dependencies中
  • 删除原项目中的node_modules依赖,gulpfile.js中的内容以及devDependencies中的内容
  • 新项目链接至全局执行命令: yarn link
  • 原项目内引入新项目,执行命令: yarn link “项目名”
  • 原项目gulpfile.js中引入:module.exports=require(‘项目名’)
  • 新项目安装依赖执行命令:yarn

    解决模块中的问题

  • 为了测试,先安装gulp和gulp-cli,执行命令:yarn add gulp gulp-cli —D

  • 执行命令:yarn build,出现cannot find package.json
  • 解决:
    • 原目录下新建文件pages.config.js文件解决,引用文件中的不应该出现的公共部分
    • 新项目lib/index.js中data移动至原项目pages.config.js,具体请看新项目中lib/index.js下config部分代码
  • 执行命令:yarn build,出现cannot find modules @babel/preset-env
  • 解决:
    • 新项目lib/index.js中使用babel/preset-env的部分改为require(‘@babel/preset-env’)
  • 执行命令:yarn build,即可正常执行

    抽象路径配置

  • 修改所有写死的路径至配置中

包装gulp cli

……

发布并使用模块

……

总结

……

FIS

基本使用

核心:高度集成

编译压缩

作业

简答题

1、谈谈你对工程化的初步认识,结合你之前遇到过的问题说出三个以上工程化能够解决问题或者带来的价值。

答:前端工程化是遵循一定的标准和规范,通过工具去提高效率降低成本的一种手段。初步认为是将大文件拆分成一些相互依赖的小文件,再进行拼接和加载。
问题/价值:

  • 对资源的统一管理
  • 规范化编码结构
  • 极大提升开发效率
  • 对前后端分离更加有利
  • ……

2、你认为脚手架除了为我们创建项目结构,还有什么更深的意义?

答:脚手架简单来说就是自动为我们创建项目基础文件的一个工具,我们可以利用脚手架工具,快速搭建特定类型的项目骨架,除了创建项目基础结构,脚手架也为项目提供了一些约定和规范。可以基于骨架和相应的约定和规范进行后续的开发工作。

编程题

1、概述脚手架实现的过程,并使用NodeJS完成一一个自定义的小型脚手架工具

脚手架实现的过程:

  • 构建项目
  • 构建相关命令
  • 通过命令行交互询问用户问题
  • 根据用户回答的结果生成文件

具体代码请看code-01文件夹

2、尝试使用Gulp完成项目的自动化构建

具体代码请看code-02文件夹

3、使用Grunt完成项目的自动化构建

具体代码请看code-03文件夹

2-3 题基础代码下载地址:https://github.com/lagoufed/fed-e-code/tree/master/part-02/module-01/作业案例基础代码.zip?raw=true

说明:
本次作业的中的编程题要求大家完成相应代码过后,录制一一个小视频简单介绍一下实现思路,演示一下相应功能。最终将录制的视频和代码统一提交至作业仓库。