简答题
1、谈谈你对工程化的初步认识,结合你之前遇到过的问题说出三个以上工程化能够解决问题或者带来的价值。
工程化是一个可以提升开发体验、提高开发效率和质量的规划或者工作流架构,一切以提高效率、降低成本、质量保证为目的的手段都属于工程化。
工程化带来的价值:
- 开发时可以使用 ES6+ 新特性,通过工程化的手段将新语法转换成兼容性好的语法,然后发布
- 可以使用热更新提升开发体验和效率,可以将代码压缩等这样重复机械的工作交给计算机完成
- 团队协作开发时,可以使用一些编码规范检查的工具,使得项目代码风格统一,质量得到保证
- 可以使用 Mock.js 这样的插件完成假数据的编写,开发阶段时,让前端可以不依赖后端接口去完成相应工作
2、你认为脚手架除了为我们创建项目结构,还有什么更深的意义?
更深的意义在于,脚手架也为我们提供了项目规范和公共约定。包括相同的组织结构、相同的开发范式、相同的模块依赖、相同的工具配置、相同的基础代码等等,对于公司里的大多数产品,前端都可以使用同一套脚手架,不仅可以统一各个项目,当项目成员切换团队时,也可以直接上手,提高效率,相关模块依赖更新或者配置需要改动时,可以一步到位更新所有产品,更利于维护。
编程题
1、概述脚手架实现的过程,并使用 NodeJS 完成一个自定义的小型脚手架工具
脚手架的实现流程
- 通过命令行交互询问用户问题;
- 根据用户回答的结果,再结合一些模板文件,最后生成项目结构。
基于 NodeJS 的自定义小型脚手架工具 lxc-scaffolding,参见 https://github.com/luxiancan/...
2、尝试使用 Gulp 完成 项目 的自动化构建
参见 https://github.com/luxiancan/...
3、使用 Grunt 完成 项目 的自动化构建
还没完成。。。
项目文件说明
https://github.com/luxiancan/...
- notes : 笔记
- lxc-scaffolding : 使用 NodeJS 完成的一个小型脚手架
- lxc-gulp-proj : 使用 Gulp 完成项目的自动化构建案例
- introduce_video.m4v : 实现思路的视频介绍
学习笔记
前端工程化
通过工程化提升战斗力
工程化主要解决的问题
- 传统语言或语法的弊端
- 无法使用模块化、组件化
- 重复的机械式工作
- 代码风格统一、质量保证
- 依赖后端服务接口支持
- 整体依赖后端项目
一切重复的工作都应该被自动化
创建项目 => 编码 => 预览/测试 => 提交 => 部署
工程化不等于工具
一些成熟的工程化集成
- create-react-app
- vue-cli
- angular-cli
- gatsby-cli
脚手架工具
前端工程化的发起者
脚手架工具的本质作用:创建项目基础结构、提供项目规范和约定。
- 相同的组织结构
- 相同的开发范式
- 相同的模块依赖
- 相同的工具配置
- 相同的基础代码
常用的脚手架工具
- React 项目 -> create-react-app
- Vue 项目 -> vue-cli
- Angular 项目 -> angular-cli
它们可以根据信息创建对应的项目基础结构
Yeoman: 一款通用型脚手架工具,创建项目时,可以根据模板生成对应的项目结构,很灵活、易于扩展
Plop: 用于在开发过程中,创建一些特定类型的文件
Yeoman
Yeoman 基础使用
- 在全局范围安装 yo
$ npm install yo --global # or yarn global add yo
- 安装对应的 generator
$ npm install generator-node --global # or yarn global add generator-node
- 通过 yo 运行 generator
$ cd path/project-dir
$ mkdir my-module
$ cd my-module
$ yo node
Yeoman 使用步骤总结
- 明确你的需求;
- 找到合适的 Generator;
- 全局范围安装找到的 Generator;
- 通过 Yo 运行对应的 Generator;
- 通过命令行交互填写选项;
- 生成你所需要的项目结构;
自定义 Generator
Generator 基本结构
├── generators/ ·········································· 生成器目录
│ ├── app/ ············································· 默认生成器目录
│ | └── index.js ···································· 默认生成器实现
| └── component/ ······································· 其他生成器目录
│ └── index.js ···································· 其他生成器实现
└── package.json ········································· 模块包配置文件
自定义 Generator 的详细操作步骤:
1.创建 generator 文件目录
$ mkdir generator-lxc-test
$ cd generator-lxc-test
2.给项目初始化一个 package.json$ npm init -yes # or yarn init -yes
3.安装 yeoman 依赖$ yarn add yeoman-generator
4.新建 generator 入口文件$ code generators/app/index.js
// 此文件作为 Generator 的核心入口
// 需要导出一个继承自 Yeoman Generator 的类型
// Yeoman Generator 在工作时会自动调用我们在此类型中定义的一些生命周期方法
// 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入
const Generator = require('yeoman-generator');
module.exports = class extends Generator {
prompting () {
return this.prompt([
{
type: 'input',
name: 'name',
message: 'Your project name',
default: this.appname // appname 为项目生成的目录名称
}
])
.then(answers => {
// answers => { name: 'user input value' }
this.answers = answers;
});
}
// Yeoman 自动在生成文件阶段调用此方法
// 我们这里尝试往项目中写入文件
writing () {
this.fs.write(
this.destinationPath('temp.txt'),
Math.random().toString()
)
}
}
5.如果使用到模板,需要在 app 目录下新建 templates 目录存放模板文件,使用 EJS 语法写入变量
这是一个模板文件
内部可以使用 EJS 模板标记输出数据
例如:<%= title %>
其他的 EJS 语法也支持
<% if (success) { %>
哈哈哈
<% }%>
6.完成 index.js 后,将我们创建的 generator 链接到全局,在 generator-lxc-test 目录下执行:$ yarn link
7.使用我们创建好的 generator
$ cd path/project-dir
$ mkdir my-test-proj
$ cd my-test-proj
$ yo lxc-test
Plop
- 一个小而美的脚手架工具,一般用于创建项目中特定类型文件的小工具;
- 可以大大提高我们在项目中创建重复文件的效率;
- 一般不会独立使用,会集成到项目中,自动化的创建同类型的文件;
Plop 的基本使用
1.安装 plop $ yarn add plop --dev
2.在项目根目录下创建模板文件 plop-templates/component.hbs
{{name}} Component
3.在项目根目录下创建 plop 入口文件 plopfile.js
// plopfile.js
// Plop 入口文件,需要导出一个函数
// 此函数接收一个 plop 对象,用于创建生成器任务
module.exports = plop => {
plop.setGenerator('component', {
description: 'create a component',
prompts: [
{
type: 'input',
name: 'name',
message: 'component name',
default: 'MyComponent'
}
],
actions: [
{
type: 'add', // add 代表添加文件
path: 'src/components/{{name}}/{{name}}.vue',
templateFile: 'plop-templates/component.hbs'
}
]
})
}
4.利用我们刚刚注册的生成器的名字启动 plop$ yarn plop component
自动化构建
一切重复工作本应自动化
源代码 => 自动化构建 => 生产代码
自动化构建工作流
- 作用:脱离运行环境兼容带来的问题
-
使用提高效率的语法、规范和标准
- ECMAScript Next
- Sass
- 模板引擎
- 构建转换那些不被支持的特性
自动化构建初体验
利用 package.json 中的 script 属性,配置一些简单的自动构建命令
"scripts": {
"build": "sass scss/main.scss css/style.css --watch",
"serve": "browser-sync . --files \"css/*.css\"",
"start": "run-p build serve"
},
常用的自动化构建工具
- Grunt ,最早的前端构建系统,工作过程是基于临时文件实现的,所以构建速度相对较慢,插件生态非常完善
- Gulp ,构建是基于内存实现的,支持同时执行多个任务,效率较高,使用方式更直观易懂,插件生态也很完善
- FIS ,百度的前端团队推出的前端构建系统,相当于捆绑套餐,将一些典型的需求都集成在内部
Grunt 的使用
Grunt 的基本使用
- 先要安装 grunt ,yarn add grunt
- 在项目根目录创建 Grunt 的入口文件 gruntfile.js
- 用于定义一些需要 Grunt 自动执行的任务
- 需要导出一个函数
- 此函数接收一个 grunt 的形参,内部提供一些创建任务时可以用到的 API
// gruntfile.js
module.exports = grunt => {
grunt.registerTask('foo', () => {
console.log('hello grunt~');
});
grunt.registerTask('bar', '任务描述', () => {
console.log('other task~');
});
grunt.registerTask('default', ['foo', 'bar']);
// 执行异步任务
grunt.registerTask('async-task', function () {
const done = this.async();
setTimeout(() => {
console.log('async task working~');
done();
}, 3000);
});
}
Grunt 标记任务失败
- 在 registerTask 第二个参数,也就是回调函数里 return false 即可
- 处理多个任务,其中一个任务失败的情况,可以用 --force 命令强制执行所有任务
- 异步任务,标记失败的处理:
const done = this.async(); ... done(false);
Grunt 的配置方法
- 使用 grunt.initConfig() 这个方法进行配置
- 在 registerTask 第二个参数,也就是回调函数里通过 grunt.config('foo') 获取配置
module.exports = grunt => {
grunt.initConfig({
// foo: 'bar'
foo: {
bar: 123
}
});
grunt.registerTask('foo', () => {
// const foo = grunt.config('foo');
// console.log(foo); // bar
const bar = grunt.config('foo.bar');
console.log(bar); // 123
});
}
Grunt 多目标任务
- 多目标模式,可以让任务根据配置形成多个子任务
- 通过 grunt.registerMultiTask('build', function () {}) 执行多任务
- 通过 grunt.initConfig({ build: {...} }) 配置多个子任务
Grunt 插件的使用
- 先下载相关插件 yarn add grunt-contrib-clean
- 通过 grunt.loadNpmTasks('grunt-contrib-clean') 导入插件
- 通过 grunt.initConfig() 配置插件
module.exports = grunt => {
grunt.initConfig({
clean: {
// temp: 'temp/app.js'
// temp: 'temp/*.txt'
temp: 'temp/**'
}
});
grunt.loadNpmTasks('grunt-contrib-clean');
}
Grunt 常用插件介绍
yarn init -yes
yarn add grunt grunt-sass sass --dev
创建 gruntfile.js 文件
yarn grunt sass
yarn add grunt-babel @babel/core @babel/preset-env --dev
yarn add load-grunt-tasks --dev
Gulp 的使用
Gulp 的基本使用
yarn init -yes
yarn add gulp
创建 gulpfile.js 文件
yarn gulp my-task 执行指定任务
yarn gulp 执行默认的 default 任务
// gulpfile.js gulp 的入口文件
exports.foo = done => {
console.log('foo task working~');
done(); // 标识任务完成
}
exports.default = done => {
console.log('default task working~');
done();
}
// gulp 4.0 版本之前,创建任务的方式
const gulp = require('gulp');
gulp.task('bar', done => {
console.log('bar working~');
done();
});
Gulp 的组合任务
const { series, parallel } = require('gulp');
const task1 = done => {
setTimeout(() => {
console.log('task1 working~');
done();
}, 1000);
}
// ...
// 创建串行任务
exports.foo = series(task1, task2, task3);
// 创建并行任务
exports.bar = parallel(task1, task2, task3);
Gulp 的异步任务
exports.promise = () => {
console.log('promise task~');
return Promise.resolve();
}
exports.promise_error = () => {
console.log('promise task~');
return Promise.reject(new Error('task failed!'));
}
const timeout = time => {
return new Promise(resolve => {
setTimeout(resolve, time);
});
};
exports.async = async () => {
await timeout(1000);
console.log('async task~');
}
// 处理文件流
const fs = require('fs');
exports.stream = done => {
const readStream = fs.createReadStream('package.json');
const writeStream = fs.createWriteStream('temp.txt');
readStream.pipe(writeStream);
readStream.on('end', () => {
done();
});
}
Gulp 构建过程核心原理
输入 => 加工 => 输出
读取流 => 转换流 => 写入流