前端工程化

发布于:2024-04-26 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、⼯程化脚⼿架

1、脚⼿架的应⽤与回顾

  • Vue react 现代框架的脚⼿架回顾
  • ⽤户交互与应⽤⽣成
  • 实际项⽬中的应⽤

工程化

1、狭义: 开发流程

  • 分支管理
  • 开发环境
  • 单元测试\自动化测试
  • 迭代部署: ci c持续迭代 cd 持续部署
    cicd: devops 自动化的开发运维方式(自己的项目 基础类型 自动化测试脚本 自动化部署脚手架 )

2、广义

  • 大范围:开发 发布 运维 维护 后期bug fix整个流程

开发

  • 框架选型:
  • 不同框架的对比:
  • 模块化+组件化:工程化的基石
  • 脚手架:项目团队所有的项目产生一个连接 -> 本地服务 mock服务一致的情况下生成一套框架 每一个项目都对齐
  • 本地开发服务:dev server
  • mock 服务:模拟 api doc

构建

  • 打包、依赖、代码处理
  • 缓存、增量更新(只更新增量的部分)、资源处理(静态资源、图片资源、插件处理):性能方面的处理
  • Es & babel | css & css processor:编译
  • 类库打包、工具资源的应用:合并工具 避免重复打包
    构建性能优化

部署

  • 持续部署
  • nginx反向代理、spa路由配置
  • 跨域、http证书、流量:如何统计流量 网关:统一的配置
  • 环境:如何区分不同的环境 -> 不同 ip -> 不同路由 -> 配置host

性能和规范

  • 缓存策略:网络层级(强缓存 协商缓存 304)
    网络层面(cdn更新策略: 定时更新 监听某个具体文件 )+文件层面(hash本地文件)(前后端)构建层面缓存
  • CDN:内容分发网络 -> 在物理上将内容放到更近的不同的节点上 -> 距离服务器更近的获取资源更高效
  • 请求:内容压缩、请求合并、数量减少、使用长连接、http2 帧传输、限制6个tcp连接、建立tcp连接优化
  • 日志性能:定位问题 发现为题
  • 预加载:骨架屏 工程化上给到业务的一个模板

sdk: 封装 兼容性
cdn:
为什么webpack写的配置回去改变我们的代码 怎么给webpack写插件

二、前端AST应⽤

AST的概念: 抽象语法树

  • a. ⽣成过程
    code ->(简单粗暴的处理方式: 比如字符串替换) ast (工程化的预处理: AST generator)-> code
  • b. 词法分析 & 语法分析
  • c. AST基础处理
  • d. babel基础

脚手架

  • 快速自动化搭建启动项目工具 批量化生产的作用

目标:

    // 命令行直接创建基于模版的项目
    // vue create ${name}

手写脚手架

第一步:处理依赖
    npm i path		// 路径处理/合并
    npm i chalk@4.1.0		// 控制台样式高亮
    npm i fs-extra		// file system模块的拓展
    npm i inquirer@8.2.4		// 命令行输入参数的 轮询 跟用户的交互 补充文件
    npm i commander		// 在主命令下面生成自定义的指令
    npm i axios		// 网络请求拿模板什么的
    npm i download-git-repo		// 下载远程的模板文件 当成一个项目

    // ora 控制台的loading 
    // easy-table 控制台输出表格
    // figlet 打logo
第二步:处理工程入口
    // 1. 初始化npm
    npm init

    // 2. 新建主命令(主入口文件),配置项更新
    // bin + package.json

    // 3. 关联主命令与配置项
    npm link   
    // package.json 中添加 "bin": "bin/zwCli.js"
    #! /usr/bin/env node  // 声明全局的可执行的主入口 基于node
const program = require('commander') //自定义指令

// 定义命令和参数
// create命令
program.command('create <app-name>').description('create a new project').option('-f, --force', 'overwrite target directory if it exist').action((name, options) => {
    // 打印执行结果
    console.log('program name is', name)
    require('../lib/create')(name, options)
})

// 解析用户执行命令的传入参数
program.parse(process.argv)

    // 4. 执行主命令即可关联逻辑内容
    // zw-cli: 主命令 已经被npm link注入到了全局
第三步:加入命令交互

交互好帮手 - commander

    // 创建命令 - create
    program.command('create <app-name>') 	// 新建app的名称
    .description('create a new project'// 描述
    .options('-f, --force', 'overwrite target directory if it exist') 	// 附加的配置
    .action((name, options) => {
    	// 执行
    	// 打印执行结果
   		console.log('program name is', name)
    	require('../lib/create')(name, options)
	})
	// 解析用户执行命令的传入参数
	program.parse(process.argv)

    // 4. 执行主命令即可关联逻辑内容
    // zw-cli: 主命令 已经被npm link注入到了全局
lib 文件夹:实现各种能力

lib/create

//  用户的操作 交互
const path = require('path')
const fs = require('fs-extra')
const inquirer = require('inquirer')
const Generator = require('./generator')
// 1. 对外抛出一个方法用来接收用户要创建的文件项目名以及参数
module.exports = async function(name, options) {
    // 判断项目是否存在
    const cwd = process.cwd()
    const targetAir = path.join(cwd, name)
    // 目录是否存在
    if (fs.existsSync(targetAir)) {
        // 是否为强制创建
        if (options.force) {
            await fs.remove(targetAir)
        } else {
            // 询问用户是否确定要覆盖
            let { action } = await inquirer.prompt([
                {
                    name: 'action',
                    type: 'list',
                    message: 'Target directory already exists',
                    choices: [{
                        name: 'Overwrite',
                        value: 'overwrite'
                    }, {
                        name: 'Cancel',
                        value: false
                    }]
                }
            ])
            // 如果用户拒绝覆盖则停止生成操作
            if (!action) {
                return;
            } else if (action === 'overwrite') {
                await fs.remove(targetAir)
            }
        }
    }
    // 新建模板
    const generator = new Generator(name, targetAir)
    generator.create()
}

// generator.js 最终生成
const { getRepoList, getTagList } = require('./http')

const ora = require('ora')
const inquirer = require('inquirer')
const util = require('util') // promisfy:promise化
const downloadGitRepo = require('download-git-repo')  // 回调函数形式 不支持promise、 需要promise化
const path = require('path')
const chalk = require('chalk')

// 封装loading外壳
async function wrapLoading(fn, message, ...args) {
    const spinner = ora(message)

    spinner.start()

    try {
        const result = await fn(...args)

        spinner.succeed()
        return result
    } catch (error) {
        spinner.fail('Requiest failed, please refetch...')
    }
}

// 生成模板的类 实例化
class Generator {
    constructor(name, targetDir) {
        this.name = name
        this.targetDir = targetDir
        this.downloadGitRepo = util.promisify(downloadGitRepo)
    }

    // 获取用户选择的模版
    // 1. 从远端拉模版数据
    // 2. 用户去选择自己已有下载的模版名称
    // 3. 返回用户选择的模版
    async getRepo() {
        const repoList = await wrapLoading(getRepoList, 'waiting for fetch template')
		// 如果没有拿到模板 退出
        if (!repoList) return
		// 模板的名字是索引模板的唯一关键字
        const repos = repoList.map(item => item.name)
        // 让用户去选择自己新下载的模版名称
        const { repo } = await inquirer.prompt({
            name: 'repo',
            type: 'list',
            choices: repos,
            message: 'Please choose a template to create project'
        })
        return repo
    }

    // 获取用户选择的版本
    // 1. 基于repo的结果,远程拉版本列表
    // 2. 自动选择最新的tag
    async getTag(repo) {
        const tags = await wrapLoading(getTagList, 'waiting for fetch tag', repo)
        if (!tags) return
        const tagsList = tags.map(item => item.name)      
        return tagsList[0]
    }

    // 下载远程模版
    // 1. 拼接下载地址
    // 2. 调用下载方法
    async download(repo, tag) {
        const requestUrl = `FEcourseZone/${repo}${tag ? '#'+tag : ''}`
        await wrapLoading(
            this.downloadGitRepo,
            'waiting download template',
            requestUrl,
            path.resolve(process.cwd(), this.targetDir)
        )
    }

    // 核心创建逻辑
    async create() {
        // 1. 获取模版名称
        const repo = await this.getRepo()
        // 2. 获取tag名称
        const tag = await this.getTag(repo)
        // 3. 下载模版到目录
        await this.download(repo, tag)
        console.log(`\r\nSuccessfully created project ${chalk.cyan(this.name)}`)
    }
}
module.exports = Generator

// 网络请求:拉取模板
const axios = require('axios')
// 拦截器
axios.interceptor.response.use(res=>{
	return res.data
})
// 获取模板列表
async function getPepoList(){
	return axios.get('https://api.github.com/orgs/FEcourseZone/repos')
}
// 获取版本信息
async function getTagList(repo) {
    return axios.get(`https://api.github.com/repos/FEcourseZone/${repo}/tags`)}
module.exports = {
	getRepoList
}

执行
zw-cli
在这里插入图片描述
zw-cli create app
在这里插入图片描述
npm publish 封装到npm包里

三、⼯程化webpack

1. 从零配置webpack

  • a. ⼯具的三⼤件
  • b. Webpack⼯作模式
  • C. 模块化打包的实现

2. webpack异步玩法

  • a. webpack异步加载的原理
  • b. 路由懒加载的重要意义
  • c. webpack的分包策略
  • d. 重点⾯试题以及⾯试官到底想问什么

3. webpack的HMR

  • a. 什么是HMR
  • b. webpack集成HMR
  • c. 实现原理与流程
  • d. 为什么⾯试官总是喜欢问HMR

4. webpack的loader与插件

  • a. 编写webpack的loader
  • b. 编写webpack的插件