文章内容输出来源:拉勾教育 大前端高薪训练营
前端工程化
基本介绍
定义
前端工程化是指遵循一定的标准和规范,通过工具去提高效率,降低成本的一种手段。
面临的问题
1、想要使用 ES6 + 新特性,但是兼容有问题
2、想要使用 Less / Sass / PostCSS 增强 CSS 的编程性,但是运行环境不能直接支持
3、想要使用模块化的方式提高项目的可维护性,但运行环境不能直接支持
4、部署上线前需要手动压缩代码及资源文件,部署过程需要手动上传代码到服务器
5、多人协作开发,无法硬性统一大家的代码风格,从仓库中 pull 回来的代码质量无法保证
6、部分功能开发时,需要等待后端服务接口提前完成
主要解决的问题
1、传统语言或语法的弊端
2、无法使用模块化/组件化
3、重复的机械式工作
4、代码风格统一、质量保证
5、依赖后端服务接口支持
6、整体依赖后端项目
表现形式
一切以提高效率、降低成本、质量保证为目的的手段都属于工程化。
-
1,工程化的具体表现
一切重复的工作都应该被自动化。实现前端工程化可以从 模块化、组件化、规范化、自动化等方面出发。
-
2,工程化不等于工具
一些成熟的工程化集成:create-react-app、vue-cli、angular-cli、gatsby-cli。
脚手架工具
基本介绍
简单来说,脚手架就是用来自动的去帮我们创建项目基础文件的一个工具。脚手架工具,可以认为是前端工程化的发起者。
作用
创建项目基础结构、提供项目规范和约定。
-
一般来说,一个项目中包含一些相同的约定,即:
1)相同的组织结构
2)相同的开发范式
3)相同的模块依赖
4)相同的工具配置
5)相同的基础代码
我们可以通过脚手架工具搭建一个特定的项目骨架,然后基于这个骨架去进行相关的开发工作。但是,前端脚手架是以独立的工具存在,不会集成在IDE中。
常用工具
Yeoman
基本介绍
准备工作
-
1,全局安装,可以使用 yarn / npm / cnpm 命令
$ yarn global add yo # or npm install yo --global
-
2,Yeoman 需要搭配特定的 Generator 使用,在这里使用的node_module,因此还需要全局安装 generator-node
$ yarn global add generator-node # or npm install generator-node --global
注意
如果出现无法识别 yo 命令的问题,请查看yarn的配置,里面详细讲述了解决方案。
基本步骤
-
1,明确你的需求;
-
2,找到合适的 Generator;
-
3,全局范围安装找到的 Generator;
-
4,通过 Yo 运行对应的 Generator;
-
5,通过命令行交互填写选项;
-
6,生成你所需要的项目结构;
Generator
-
使用 Yeoman 提供的 yo 命令,去运行 generator-node 生成器。运行特定的生成器,就是将 generator- 前缀去掉,直接使用 yo 运行后面的部分即可。
$ mkdir project-dir-name $ cd path/to/project-dir $ yo node
运行结果,如下图所示:
项目结构,如下图所示:
Sub Generator
在已有的项目结构中,创建一些特定类型的文件,可以使用 Yeoman 提供的 Sub Generator 进行实现。
-
1,使用 generator-node中的子集的生成器,即cli生成器,它可以生成cli所需要的一些文件。
命令代码如下:
$ cd path/to/project-dir $ yo node:cli # or yo + generator的名字 + : + Sub Generator的名字
运行结果,如下图所示:
-
2,通过以下命令,将这个模块连接到全局范围,使其可以作为全局的命令行模块进行使用。
$ yarn link # or npm link
可以看到,此时会将新的模块生成的依赖包,存放到 Yarn 应用程序的本地数据文件夹中。
并且,会将新的模块的cmd脚本文件,注册到 yarn global bin 的路径下。
-
3,最后,需要安装项目依赖。
$ yarn
注意
每个用户的bin目录不同,cmd命令存放不同,因此,配置的环境变量路径也不同。
查看生成的 cmd 脚本文件,如下图所示,无法找到 link 后的模块。
解决方案,详见【https://blog.csdn.net/zimeng303/article/details/109736592】 -
4,全局访问生成的模块
$ module-name --help
运行结果,如下图所示:
注意并不是每一个 generator 都存在子集的生成器,需要以官方文档为准。
自定义 Generator
基于 Yeoman 搭建自己的脚手架,Generator 本质上就是一个 NPM 模块。
命名规范
generator- name
注意
如果命名不规范,那么后期 Yeoman 就无法找到所定义的 生成器模块。
基本步骤
创建 Generator 模块
-
1,创建的 Generator 目录结构,如下图所示:
-
2,创建的 Sub Generator 目录结构,component 文件夹存放子集生成器,如下图所示:
创建 Generator 步骤
-
1,创建 Generator 的文件夹,命名要符合命名规范,如 generator-sample
$ mkdir generator-sample
-
2,使用 yarn 或者 npm ,创建并初始化 package.json 文件
$ cd generator-sample $ yarn init # or npm init
-
3,安装 yeoman-generator 的模块,此模块提供生成器的基类,此基类又提供了工具函数,可以使在创建 Generator 时更加便捷。
$ yarn add yeoman-generator # or npm install yeoman-generator
-
4,生成器的入口文件 index.js 所在目录结构,如下图所示:
-
5,入口文件 index.js ,需要导出一个继承自 Yeoman Generator 的类型。
代码示例如下:
// index.js : 此文件作为 Generator 的核心入口 // 引入 yeoman-generator,以便可以使用其内部的基类,从而使用其工具函数... const Generator = require('yeoman-generator') // 导出一个继承自 Yeoman Generator 的类型 module.exports = class extends Generator { writing () { // Yeoman 自动在生成文件阶段调用此方法 // 我们这里尝试往项目目录中写入文件 this.fs.write( // 通过父类中的fs模块进行写入,与 node中的不一样 // 写入文件的绝对路径 this.destinationPath('temp.txt'), // destinationPath 自动获取,生成项目目录下对应的文件路径 Math.random().toString() // 写入文件内容 ) } }
可以看到,Yeoman Generator 在工作时,会自动调用我们在此类型中定义的一些生命周期方法,我们在这些方法中可以通过调用父类提供的一些工具方法,去实现一些功能,例如文件导入等。
-
6,通过 yarn link 或者 npm link 的方式,将自定义的 Generator 模块,链接到全局范围,使之成为一个全局模块包。
$ yarn link # or npm link
我的 yarn 本地链接到的是在 C盘,如下图:
-
7,新建项目目录,进入目录,执行 yeoman 操作
$ cd path/to/project-dir $ yo sample
注意
由于我的 yarn 本地链接到的是在 C盘,直接使用上面的操作会报出如下错误:
此时,有三种解决方案。一, 将 link的 generator-sample拷贝到yarn的全局安装目录中,如下图所示:
**二,**在执行上述操作之前,先将 generator-sample 链接到当前文件夹下,命令如下:$ cd path/to/project-dir $ yarn link generator-sample $ yo sample
**三,**使用命令,将其在全局范围内安装
$ npm install --force -g generator-sample
通过上面的操作,便可以使 Yeoman 找到这个 generator-sample 依赖包了。
个人推荐第二种或第三种运行结果,如下图所示:
执行后,会在项目目录下写入 temp.txt 文件,并在文件内写入 随机数内容,如图所示:
根据模板创建文件
根据模板创建文件,相对于手动创建每一个文件,模板的方式大大提高了效率。
-
1,模板文件内部可以使用 EJS 模板标记输出数据,如:
<%= title %>
其他的 EJS 语法也支持,如:
<% if (success) { %> 哈哈哈 <% } %>
-
2,使用模板文件代替直接写入的方式
代码示例如下(index.js):
module.exports = class extends Generator { writing () { // Yeoman 自动在生成文件阶段调用此方法 // 通过模板方式写入文件到目标目录 // 模板文件路径 const tmpl = this.templatePath('foo.txt') // 输出目标文件 const output = this.destinationPath('foo.txt') // 输出数据上下文 const context = { title: 'Hello zce~', success: false } this.fs.copyTpl(tmpl, output, context) } }
-
3,动态接收用户输入数据
代码示例如下(index.js):
const Generator = require('yeoman-generator') module.exports = class extends Generator { prompting () { // Yeoman 在询问用户环节会自动调用此方法 // 在此方法中可以调用父类的 prompt() 方法发出对用户的命令行询问 // 接收一个数组参数,数组的每一项都是一个问题对象 return this.prompt([ // 返回一个 Promise对象 { type: 'input', // 使用用户输入的方式,让用户提交信息 name: 'name', // 最终得到结果的一个键 message: 'Your project name', // 在界面中给用户的提示,即问题 default: this.appname // appname 为项目生成目录名称 } ]).then(answers => { //用户输入之后的结果 // answers => { name:'user input value' } this.answers = answers // 便于writing时使用 }) } writing () { // Yeoman 自动在生成文件阶段调用此方法 // 通过模板方式写入文件到目标目录 // 模板文件路径 const tmpl = this.templatePath('bar.html') // 输出目标文件 const output = this.destinationPath('bar.html') // 输出数据上下文 // 此时使用用户输入数据,作为模板文件的上下文 const context = this.answers this.fs.copyTpl(tmpl, output, context) } }
在 templates 目录下面新建 bar.html,并使用 ESJ 模板引擎的写法。
代码示例如下:
其中的 “name” 为在index.js中 prompt() 方法中传入的数组参数元素中 name 的值。
Vue Generator 案例
创建 Generator
-
1,创建 generator-vue文件夹,并创建及初始化 package.json 包管理文件,以及安装 yeoman-generator 模块
$ mkdir generator-vue $ cd generator-vue $ yarn init # or npm init $ yarn add yeoman-generator # or npm install yeoman-generator -D
-
2,将 vue中生成的目录结构,拷贝到 Generator 中,使其作为模板文件存在,如下图所示:
-
3,根据自定义 Generator 步骤,在 index.js 入口文件中,添加 prompting() 方法和writing()方法,prompting() 方法代码如上,此处只书写 writing() 方法。
代码示例如下:
const Generator = require('yeoman-generator') module.exports = class extends Generator { prompting() { // ... , 代码同上 } writing() { // 把每一个文件都通过模板转换到目标路径 const templates = [ '.browserslistrc', '.editorconfig', '.env.development', '.env.production', '.eslintrc.js', '.gitignore', 'babel.config.js', 'package.json', 'postcss.config.js', 'README.md', 'public/favicon.ico', 'public/index.html', 'src/App.vue', 'src/main.js', 'src/router.js', 'src/assets/logo.png', 'src/components/HelloWorld.vue', 'src/store/actions.js', 'src/store/getters.js', 'src/store/index.js', 'src/store/mutations.js', 'src/store/state.js', 'src/utils/request.js', 'src/views/About.vue', 'src/views/Home.vue' ] templates.forEach(item => { // item => 每个文件路径 // 使用fs的copyTpl() 方法,将每一个对应的模板生成到对应的文件 this.fs.copyTpl( this.templatePath(item), this.destinationPath(item), this.answers ) }) } }
-
4,使用 generator-vue 方法,同自定义Generator 使用方法,执行结果,如下图所示:
发布 Generator
-
1,在发布之前,首先将项目的源代码托管到公开的代码仓库里面,这里我们使用GitHub。
具体操作,详见【GIT关联本地仓库与远端仓库】
-
2,在 命令行界面输入发布命令。
$ yarn publish # or npm publish
运行结果,如下图所示:
如果使用的镜像资源超时,会报出上面的错误,此时需要修改请求地址。解决方案详见:【yarn publish 报错】
注意
在第一次执行时,会要求输入npm 的用户名、邮箱和密码,后期输入,只需要输入密码即可。
登录、登出命令如下:
$ yarn login # or npm login # 登录命令 $ yarn logout # or npm logout # 登出命令
-
3,若使用淘宝的镜像资源,则会报错下面的错误,这是由于淘宝镜像是只读的,不能将模块进行发布。
解决方案1,可以改变镜像资源
$ npm config set registry https://registry.yarnpkg.org # yarn 镜像源 $ npm config set registry https://registry.npmjs.org # node 默认镜像源
2,在命令后面,直接跟上相关的镜像资源
$ yarn publish --registry-https://registry.yarnpkg.com # yarn 镜像源 $ yarn publish --registry-https://registry.npmjs.org # node 默认镜像源
成功运行结果,如下图所示:
若还有其他报错,请查看【yarn publish 报错】 -
4,可以去 npm官网,查看刚刚发布的模块,如下图所示:
-
5,使用 npm / yarn 进行全局范围的安装,并使用 yo 命令运行这个 Generator。
Plop
基本介绍
一个小而美的脚手架工具,创建项目中特定类型的文件的小工具。类似于Yeoman中的Sub Generator,但是他一般不会独立使用,而是集成在项目中,用来自动化的创建同类型的项目文件,例如 创建一个组件 / 模块所需要的文件。
Plop 可以提高创建重复文件的效率。
基本步骤
1,将 plop 模块作为项目开发依赖安装;
2,在项目根目录下创建一个 plopfile.js 文件;
3,在 plopfile.js 文件中定义脚手架任务;
4,编写用于生成特定类型文件的模板;
5,通过 Plop 提供的 CLI 运行脚手架任务。
基本使用
这里以react为例,集成Plop。
-
1,全局安装 create-react-app 脚手架
$ yarn global add create-react-app # or npm install create-react-app -g
-
2,使用 create-react-app 脚手架,创建项目
$ create-react-app my-react-app $ cd my-react-app
-
3,在项目中集成plop,安装 plop 模块
$ yarn add plop --dev # or npm install plop -D
-
4,在项目根目录下,新建一个 plopfile.js 文件,定义 Plop 脚手架任务
代码示例如下:
// Plop 入口文件,需要导出一个函数 // 此函数接收一个 plop对象,并且提供了一系列的工具函数,用于创建生成器任务 module.exports = plop => { /** * setGenerator() 方法接收两个参数, * 一,生成器的名字 * 二,生成器的配置选项 */ plop.setGenerator('component', { description: 'create a component', prompts: [ // 指定Generator在执行时,发出的命令行问题 { type: 'input', name: 'name', message: 'component name', default: 'MyComponent' } ], // 完成命令行交互过后,需要执行的动作 actions: [ // 数组中的每一个对象,都表示一个动作对象, 可以创建多个模板 { // type指定动作的类型,add代表添加一个全新的文件 type: 'add', // path指定新添加的文件会被添加到哪一个具体的路径下 // 可以通过{{}}插值表达式的形式,去动态插入刚才用户输入的数据 path: 'src/components/{{name}}/{{name}}.js', // 指定模板文件 templateFile: 'plop-templates/components.hbs' }, { type: 'add', path: 'src/components/{{name}}/{{name}}.css', templateFile: 'plop-templates/components.css.hbs' }, { type: 'add', path: 'src/components/{{name}}/{{name}}.test.js', templateFile: 'plop-templates/components.test.hbs' } ] }) }
-
5,创建对应的模板文件,,模板文件采用Handlebars模板引擎进行书写,一般放置在根目录下的plop-templates下
代码示例如下:(components.hbs)
import React from 'React' export default () => { <div className="{{name}}"> <h1>{{name}} Component</h1> </div> }
代码示例如下:(components.css.hbs)
.{{name}} { }
代码示例如下:(components.test.hbs)
import { render, screen } from '@testing-library/react'; import {{name}} from './{{name}}'; test('renders learn react link', () => { render(<<name>> />); const linkElement = screen.getByText(/learn react/i); expect(linkElement).toBeInTheDocument(); });
-
6,在安装 plop 时,Plop 提供了一个 CLI 的应用程序,由于yarn会自动找到node_modules下的bin目录下的命令行工具,所以可以通过 yarn 去启动这个程序
$ yarn plop component # yarn plop 定义的Generator名称
执行结果,如下图所示:
上面的结果即表示执行成功,我们可以去对应的文件夹下,找到生成的文件,如下图所示:
脚手架工作原理
脚手架工具就是一个 node cli 的应用。
-
1,通过 在package.json 中添加属性 bin ,设置 入口文件,用于指定CLI应用的入口文件
-
2,安装 inquirer 模块,通过使用 inquirer 这一模块,实现用户交互
$ yarn add inquirer # or npm install inquirer
-
3,安装 ejs 模块,进行文件渲染
$ yarn add ejs # or npm install ejs
-
4,编写 cli.js 文件
代码示例如下:
#!/user/bin/env node // Node CLI 应用入口文件必须要有这样的文件头 // 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755 // 具体就是通过 chmod 755 cli.js 实现修改 // 脚手架的工作过程: // 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 => { // 每一个file都是相对于templates下的相对路径 // 通过模板引擎渲染文件,这里采用 ejs /** * renderFile() 方法 接收三个参数 * 一,文件的绝对路径 * 二,渲染的数据 * 三,回调函数 */ ejs.renderFile(path.join(tmplDir, file), answers, (err, result) => { if (err) throw err // 将结果写入目标文件路径 fs.writeFileSync(path.join(destDir, file), result) }) }) }) })