开发脚手架及封装自动化构建工作流
一、项目工程化理论
1、谈谈你对工程化的初步认识,结合你之前遇到过的问题说出三个以上工程化能够解决问题或者带来的价值。
答: 随着计算机科学技术的提升,以及web 业务日益复杂化和多元化,前端开发从 webPage 模式转变为 webApp 为主要模式了。已经不是过去的拼几个页面搞几个JQ插件就能完成了。目前来说,前端在大型项目上的开发已经非常重要了。
在开发大型项目时多会遇到这些情况:
- 想要使用Less / Sass / PostCss 增强 Css 的编程性,但是运行环境不能直接支持
- 想要使用模块化的方式提高项目的可维护性,但是运行环境不能直接支持
- 部署上线前需要手动压缩代码及资源文件,部署过程需要手动上传代码到服务器
- 多人协作开发,无法硬性统一大家的代码风格
- 从仓库中pull 回来的代码质量无法保证
- 部分功能开发时需要等待后端服务器接口提前完成
为了解决上述的常见问题,就出现了前端工程化的概念:
前端工程化是使用软件工程的技术和方法来进行前端的开发流程、技术、工具、经验等规范化、标准化,其主要目的为了提高效率和降低成本,即提高开发过程中的开发效率,减少不必要的重复工作时间。
工程化在开发过程中主要解决的问题:
- 传统语言或语法的弊端
- 无法使用模块化/ 组件化
- 重复的机械式工作
- 代码风格统一,质量保证
- 依赖后端服务接口支持
- 整体依赖后端项目
2、你认为脚手架除了为我们创建项目结构,还有什么更深的意义?
答: 脚手架的本质作用就是:创建项目基础结构、提供项目规范和约定。在创建项目过程中会出现大量的重复性工作,比如:
- 相同的组织结构
- 相同的开发范式
- 相同的模块依赖
- 相同的工具配置
- 相同的基础代码
通过脚手架,我们可以快速解决上面的重复性问题,和相同的约束规范。我们可以快速搭建项目骨架,再基于这个项目骨架进行后续的开发工作。
二、动手实操收获更多
1、概述脚手架实现的过程,并使用 NodeJS 完成一个自定义的小型脚手架工具
答:
开发准备
- mkdir scaffolding-demo
- cd scaffolding-demo
- yarn init --yes 生成 package.json 文件
- 在package.json 中添加bin 字段,指定脚手架入口文件
// package.json
{
"name": "scaffolding-demo",
"version": "1.0.0",
"main": "index.js",
"author": "mark",
"bin": "cli.js",
"license": "MIT",
"dependencies": {
"ejs": "^3.1.5",
"inquirer": "^7.3.3"
}
}
- 根目录下创建cli.js
// cli.js
#!/usr/bin/env node //Node Cli 应用入口文件必须要有这样的文件头
// 脚手架工作过程
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件
const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer')
const ejs = require('ejs')
inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Project name ?'
}
])
.then(answers => {
// 根据用户回答的结果生成文件
// 模版目录
const tmplDir = path.join(__dirname, 'templates')
// 目标目录
const destDir = process.cwd()
// 将模版下的文件全部转换到目标目录
fs.readdir(tmplDir, (err, files) => {
if (err) throw err
files.forEach(file => {
// console.log(file); // index.html style.css
// 通过模版引擎渲染相对路径的文件
ejs.renderFile(path.join(tmplDir, file), answers, (err, result) => {
if (err) throw err
// console.log(result);
// 将结果写入
fs.writeFileSync(path.join(destDir,file), result)
})
})
})
})
-
终端执行 yarn link ,将 cli.js link 到全局
-
安装inquirer 模块,用于发起命令行交互
yarn add inquirer --dev
- 安装模版引擎
yarn add ejs --dev
- 创建模板文件
// index.html 模版文件
// 任意什么文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= name %></title>
</head>
<body>
</body>
</html>
- 最后终端运行scaffolding-demo
2、尝试使用 Gulp 完成项目的自动化构建
Gulp构建项目源码 gitHub地址
// 实现这个项目的构建任务
const { src, dest, parallel, series, watch } = require('gulp')
// 删除文件
const del = require('del')
// 开发服务器
const browserSycn = require('browser-sync')
const bs = browserSycn.create()
const plugins = require('gulp-load-plugins')() // 自动加载模块
const data = {
menus: [
{
name: 'Home',
icon: 'aperture',
link: 'index.html'
},
{
name: 'Features',
link: 'features.html'
},
{
name: 'About',
link: 'about.html'
},
{
name: 'Contact',
link: '#',
children: [
{
name: 'Twitter',
link: 'https://twitter.com/w_zce'
},
{
name: 'About',
link: 'https://weibo.com/zceme'
},
{
name: 'divider'
},
{
name: 'About',
link: 'https://github.com/zce'
}
]
}
],
pkg: require('./package.json'),
date: new Date()
}
// 删除文件
const clean = () => {
return del(['dist', 'temp'])
}
// 编译样式
const style = () => {
return src('src/assets/styles/*.scss', { base: 'src'})
.pipe(plugins.sass({ outputStyle: 'expanded'}))
.pipe(dest('temp')) // 创建一个临时 temp 文件夹
.pipe(bs.reload({ stream: true }))
}
// 编译 JS 文件
const script = ()=> {
return src('src/assets/scripts/*.js', { base: 'src'})
.pipe(plugins.babel({presets: ['@babel/preset-env']}))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
// 编译 html 文件
const page = () => {
return src('src/*.html', { base: 'src'})
.pipe(plugins.swig(data))
.pipe(dest('temp'))
.pipe(bs.reload({ stream: true }))
}
// 压缩图片
const image = () => {
return src('src/assets/images/**', { base: 'src' })
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
// 字体文件
const font = () => {
return src('src/assets/fonts/**', { base: 'src' })
.pipe(plugins.imagemin())
.pipe(dest('dist'))
}
// 处理 public 文件夹
const extra = () => {
return src('public/**', {base: 'public'})
.pipe(dest('dist'))
}
// 开启服务器
const serve = () => {
// 监听文件变化
watch('src/assets/styles/*.scss', style)
watch('src/assets/scripts/*.js', script)
watch('src/*.html', page)
// 开发阶段不用每次都监听变化所以将他们组合成一个任务
// watch('src/assets/images/**', image)
// watch('src/assets/fonts/**', font)
// watch('public/**', extra)
watch([
'src/assets/images/**',
'src/assets/fonts/**',
'public/**'
], bs.reload)
bs.init({
notify: false,
// files: 'dist/**', // 监听 dist 文件夹下所有文件的变化
server: {
baseDir: ['temp', 'src', 'public'],
routes: {
'/node_modules': 'node_modules'
}
}
})
}
// useref 打包文件引用路径处理
const useref = () => {
return src('temp/*.html', { base: 'temp' })
.pipe(plugins.useref({ searchPath: ['temp', '.'] }))
// 压缩三种类型文件
.pipe(plugins.if(/\.js$/, plugins.uglify()))
.pipe(plugins.if(/\.css$/, plugins.cleanCss()))
.pipe(plugins.if(/\.html$/, plugins.htmlmin({
collapseWhitespace: true,
minifyCss: true,
minifyJs: true
})))
.pipe(dest('dist'))
}
const compile = parallel(style, script, page, )
const build = series(
clean,
parallel(
series(compile, useref),
image,
font,
extra
)
)
const develop = series(compile, serve)
module.exports = {
compile,
build,
clean,
serve,
develop,
useref
}
// 运行 yarn gulp develop 执行监听和打包html, css, js 文件
// 运行 yarn gulp build 执行上线打包任务
3、使用 Grunt 完成项目的自动化构建
Grunt构建项目源码gitHub地址
// 1. 安装 grunt-sass sass --dev 编译 *.scss 文件
// 2.yarn add --dev grunt-babel @babel/core @babel/preset-env 编译js es6 语法文件
// 3.yarn add load-grunt-tasks --dev // 减少loadNpmTasks 的使用
// 4.yarn add grunt-contrib-watch --dev //每当添加,更改或删除监视的文件模式时,运行预定义的任务
const sass = require('sass')
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
grunt.initConfig({
sass: {
options: {
sourceMap: true,
implementation: sass
},
main: {
files: {
'dist/assets/styles/main.css': 'src/assets/styles/*.scss'
}
}
},
babel: {
options: {
sourceMap: true,
presets: ['@babel/preset-env']
},
main: {
files: {
'dist/assets/scripts/main.js': 'src/assets/scripts/main.js'
}
}
},
watch: { // 监听文件变化
js: {
files:['src/assets/scripts/*.js'],
tasks: ['babel']
},
css: {
files:['src/assets/styles/*.scss'],
tasks: ['sass']
}
}
})
// grunt.loadNpmTasks('grunt-sass') // 载入grunt 任务
loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务
// 给多个任务做映射
grunt.registerTask('default', ['sass', 'babel', 'watch'])
}
// 运行yarn grunt 即可执行 default 任务,完成编译和监听