为何工程化
在我们的实际开发中,我们想用最新的es语法,想用less,sass等样式预处理。我们想要使用模块化的方式提高项目的可维护性,但是运行环境却不支持。多人协作,代码风格不统一。发布上线需要手动压缩与上传,这些问题的出现需要我们去有一种方式去解决这些问题。工程化就是解决这些问题的一个体现。
工程化的体现
一切以提高效率,降低成本,质量保证为目的的手段都属于工程化。
一些成熟的工程化集成 如vue-cli , angular-cli , create-react-app
前端工程化的实现 nodejs有巨大贡献。
脚手架工具yeoman的使用
1.全局安装yo
cnpm i yo -g //或者 yarn global add yo
2.安装对应的generator
npm i generator-node -g //或者 yarn global add generator-node
3.通过yo运行generator
yo node
yeoman的sub generator使用
1.明确需求。
2.找到合适的Generator。
3.全局范围安装找到的Generator。
4.通过yo安装找到的Generator。
5.通过命令交互填写选项。
6.生成所需要的项目结构。
自定义Generator
基于yeoman搭建自己的脚手架
1.创建Generator模块(generator本质上就是一个NPM模块)
yeoman的generator模块名称必须是 generator-
mkdir generator-sample //创建文件
cd generator-sample
yarn init //初始化
yarn add yeoman-generator //安装基类 提供了一些方法
按照格式创建
//作为generator核心入口
//需要导出一个继承自 yeoman generator的类型
//yeoman generator 在工作时会自动调用我们在此类型中定义的一些声明周期方法
//在这些方法中可以通过调用父类提供的一些工具方法实现一些功能 比如文件写入
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
writing() {
//yoman 自动在生成文件阶段调用此方法
//我们在这里尝试往项目目录中写入文件
// this.fs.write(
// this.destinationPath('test.txt'),
// Math.random().toString()
// )
//通过模板方式写入文件到目标目录
//三个参数 1.模板文件路径 2.输出文件路径 3.模板数据上下文
const tmpl = this.templatePath('foo.txt')//模板文件路径
const output = this.destinationPath('foo.txt')//模板文件路径
const context = { title: 'hello tem', success: true }//模板数据上下文
this.fs.copyTpl(tmpl, output, context)
}
}
通过yarn link链接到全局范围 使其成为一个全局模块包
yarn link
在其它地方就可以运行这个生成器
yo sample //刚才起的生成器名称
创建一个自己的vue Generator
mkdir generator-my-vue
cd generator-my-vue
yarn init
yarn add yeoman-generator
code . //vscode 打开文件
1.还是先创建模板文件
2.写模板代码
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
}
])
.then(answers => {
this.answers = answers
})
}
writing(){
}
}
3.将项目结构放入template文件夹中
4.将项目结构可能发生变化的地方用模板语法替换 如:
- yarn link 到全局
6.使用 yo my-vue 安装到对应目录
发布generator
//先创建一个本地的git仓库
//先创建一个gitignore
echo node_modules > .gitignore
//初始化一个本地空仓库
git init
//查看本地仓库状态
git status
git add .
//创建一次提交
git commit -m "initial commit"
//提交到远端仓库
//创建一个远端新仓库 gitee或者github
git remote add origin https://gitee.com/wkpkko/generator-myvue.git
//这样为本地仓库添加了一个远端仓库的别名 push的时候可以使用这个别名
git push -u origin master //推送到远端仓库
//通过npm publish 发布这个模块 yarn publish
yarn publish
//在使用淘宝镜像发布时会出现问题
//发布的时候设置镜像
yarn publish --registry=https://registry.yarnpkg.com
//自动推送到yarn的官方镜像 yarn的镜像与npm镜像时同步的 此时模块发布成功了
//在npm官网 npmjs.com/package/generator-myvue
//此时模块已经被推送上来了
Plop一个小而美的脚手架工具
主要用于创建项目中特定类型文件的一个小工具,类似与yeoman中的subgeneraor,一般不会单独使用。一般会把plop集成在项目中,用来自动化创建同类型的项目文件
yarn add plop --dev
在项目根目录下创建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', // 代表添加文件
path: 'src/components/{{name}}/{{name}}.js',
templateFile: 'plop-templates/component.hbs'
},
{
type: 'add', // 代表添加文件
path: 'src/components/{{name}}/{{name}}.css',
templateFile: 'plop-templates/component.css.hbs'
},
{
type: 'add', // 代表添加文件
path: 'src/components/{{name}}/{{name}}.test.js',
templateFile: 'plop-templates/component.test.hbs'
}
]
})
}
创建模板文件
plop-templates/component.hbs
import React from 'react';
export default () => (
{{name}} Component
)
component.css.hbs
.{{name}} {
}
component.test.hbs
import React from 'react';
import ReactDOM from 'react-dom';
import {{name}} from './{{name}}';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<{{name}} />, div);
ReactDOM.unmountComponentAtNode(div);
});
yarn plop component (刚才定义的生成器的名称)
脚手架工作原理
大部分脚手架都是设置一些问题,通过这些问题为你创建一些模板的项目结构。
脚手架工具就是一个nodecli应用。
通过nodejs开发一个小型的脚手架工具
mkdir sample-cli
cd sample-cli
yarn init//初始化一个package.json文件
//package.json中添加bin字段作为cli的入口文件
{
"name": "sample-cli",
"version": "1.0.0",
"description": "sample cli",
"bin":"cli.js",
"main": "index.js",
"license": "MIT"
}
创建cli.js
cli文件必须要有一个特定的文件头
#!/usr/bin/env node
// Node CLI 应用入口文件必须要有这样的文件头
// 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
// 具体就是通过 chmod 755 cli.js 实现修改
console.log('sample cli')
通过yarn link 链接到全局
此时就可以使用sample-cli命令执行
// 脚手架的工作过程:
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件
node通过用户询问 我们使用inquirer模块
yarn add inquirer
此时我们可以在cli.js载入它
const inquirer = require('inquirer')
inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Project name?'
}
]).then(answer => {
console.log(answer)
})
创建模板文件
#!/usr/bin/env node
// Node CLI 应用入口文件必须要有这样的文件头
// 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
// 具体就是通过 chmod 755 cli.js 实现修改
//脚手架的工作过程
//1.通过命令行交互询问用户问题
//2.根据用户回答的结果生成文件
const path = require('path')
const fs = require('fs')
const inquirer = require('inquirer')
inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Project name?'
}
]).then(answer => {
//根据用户回答结果生成文件
//模板目录
const templDir = path.join(__dirname,'templates')
//输出目录 命令行执行路径
const destDir = process.cwd()
//将模板下的文件全部转换到目录 fs的readdir方法会自动扫描目录下所有文件
fs.readdir(templDir,(err,files) => { //files拿到所有文件列表
if(err) throw err
files.forEach(file => {
// console.log(file) //每个file就是相对于templates下的相对路径
//可以通过模板引擎去渲染这个路径所对应的文件
})
})
})
先安装对应的模板引擎 (安装一个ejs模板引擎)
yarn add ejs
之后引入模板引擎
#!/usr/bin/env node
// Node CLI 应用入口文件必须要有这样的文件头
// 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
// 具体就是通过 chmod 755 cli.js 实现修改
//脚手架的工作过程
//1.通过命令行交互询问用户问题
//2.根据用户回答的结果生成文件
const path = require('path')
const fs = require('fs')
const inquirer = require('inquirer')
const ejs = require('ejs')
inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Project name?'
}
]).then(answer => {
//根据用户回答结果生成文件
//模板目录
const templDir = path.join(__dirname, 'templates')
//输出目录 命令行执行路径
const destDir = process.cwd()
//将模板下的文件全部转换到目录 fs的readdir方法会自动扫描目录下所有文件
fs.readdir(templDir, (err, files) => { //files拿到所有文件列表
if (err) throw err
files.forEach(file => {
// console.log(file) //每个file就是相对于templates下的相对路径
//可以通过模板引擎去渲染这个路径所对应的文件
//renderFile三个参数 1.文件的绝对路径 2.工作时的数据上下文answer 3.回调函数
ejs.renderFile(path.join(templDir, file), answer, (err, result) => {
if (err) throw err
//通过文件写入写入到目标目录
fs.writeFileSync(path.join(destDir, file), result)
})
})
})
})
资料来源:拉勾教育-前端训练营