拉勾前端高薪就业课程笔记第三弹(模块2-1)

1 篇文章 0 订阅
1 篇文章 0 订阅

一、简述

模块2-1主要的学习内容是前端工程化。包括web前端脚手架的开发、web前端项目的自动化构建。

二、主要内容

  1. 工程化

    1. 概念:遵循一定的标准和规范,通过工具提高效率降低成本
    2. 解决的问题
      1. 传统语言或语法带来的兼容问题。如最新的ES6语法、LessSass、无法兼容,运行环境不能直接支持
      2. 无法直接使用模块化/组件化
      3. 重复的机械化工作
      4. 代码风格无法统一、编码质量得不到保证
      5. 依赖后端服务接口支持
      6. 整体依赖后端项目
    3. 在开发流程中的体现
      1. 创建项目: 使用脚手架工具自动完成基本项目的搭建
      2. 编码阶段:
        1. 借助工具自动进行代码格式化和校验,保证代码的风格统一
        2. 借助编译工具使用新特性,提高开发效果
      3. 预览/测试阶段:
        1. 热更新帮助即时查看编码效果
        2. mock编写假接口,帮助开发业务功能,不用等待后台接口开发
        3. sourceMap 帮助定位问题
      4. 代码提交阶段:git hooks 在提交之前对项目的质量和风格进行检查,保证代码质量
      5. 项目部署阶段:自动部署,使用Jenkins等工具自动构建并发布
  2. web前端脚手架

    1. 脚手架的本质:脚手架本质是一个node cli应用,其作用是创建项目的基础结构,提供项目的基本规范和约定
    2. 脚手架工具
      1. 创建项目
        1. 特定语言的脚手架工具
          1. create-react-app
          2. vue-cli
          3. angular-cli
        2. 通用的脚手架工具
          1. yeoman
      2. 创建特定类型的文件
        1. Plop
    3. Yeoman脚手架工具的使用介绍
      1. Yeoman 的基本使用步骤:
        1. 全局安装: yarn global add yo
        2. 安装generatorYeoman 需要搭配generator使用,故此需要全局安装 generator,要生成什么类型的项目,就需要安装对应的generator
          1. 要安装的generator名字为 generator-generatorName。例如要生成一个node项目,需要安装一个nodegeneratoryarn global add generator-node,这里node 就是 generator 的名字。
        3. 创建文件夹,并运行 yeoman生成项目。进入创建的文件夹,并使用 yo generatorName 命令。例如 yo node,这样就会生成node一个模块的基础代码。
      2. Sub Generator:生成一些特定的文件,如 babel 的配置文件、ESLint 的配置文件 。如通过 node generator 的cli Sub Generator 生成 cli 命令需要的文件和基本代码。
        1. 运行 generatorlib Sub Generartor, 通过 yo generatorName:SubGeneratorName。例如 yo node:cli注意并不是所有的generator都有cliSubGenerator,需要通过官方文档查看。
          修改package,并创建了 lib/cli.js 文件
          修改package,并创建了 lib/cli.js 文件
          其中bin是cli文件的声明,meow是cli 命令的依赖
          其中bincli文件的声明,meowcli 命令的依赖

        2. yarn 安装依赖,并通过 yarn link/npm link将本地模块变成全局的模块,这样就能使用全局的命令运行模块。
          在这里插入图片描述

        3. 执行 yo-node --help 测试全局模块是否已生效。
          在这里插入图片描述

      3. 创建 Generator 模块:本质上就是创建 npm 模块,即一个Node模块
        1. Generator 项目的文件目录结构:
          在这里插入图片描述

        2. 创建Generator模块的步骤:

          1. 创建一个文件夹,文件夹名字必须是 generator-name 的形式。name 就是要创建的 generator 的名字。

          2. 通过 npm/yarn init 创建一个package.json 文件。其中 name 必须是 generator-name的根式,keywords 必须包含 yeoman-generator(如果要发布到 yeoman) , files 包含generator要使用的文件和文件夹的名称。

            		{
            		  "name": "generator-test",
            		  "version": "1.0.0",
            		  "description": "a simple geneartor",
            		  "main": "index.js",
            		  "scripts": {
            		    "plop": "plop"
            		  },
            		  "files": [
            		    "generator"
            		  ],
            		  "keywords": [
            		    "yeoman-generator"
            		  ],
            		  "bin": "cli.js",
            		  "author": "",
            		  "license": "ISC",
            		  "dependencies": {
            		    "plop": "^2.7.4",
            		    "yeoman-generator": "^4.12.0"
            		  }
            		}
            
          3. 安装 yeoman-generator 依赖,并创建generator模块项目结构

            1. generator 有两种项目结构:
              1. 所有文件包含在 generators 文件夹里面
                1. yo name 执行的主生成器放在generators/app/index.js中。
                2. yo name:subGenerator 执行的 SubGenerator 放在 generators/subGeneratorName/index.js中。
                3. package.jsonfiles 中只需要包含 generators
              2. 所有生成器文件夹放在根目录中
                1. yo name 执行的主生成器放在app/index.js中。
                2. yo name:subGenerator 执行的 SubGenerator 放在 subGeneratorName/index.js中。
                3. package.jsonfiles 中只需要包含 appsubGenerator
          4. 编写 generator 文件,在文件夹中的 index.js 文件中编写

            1. yeoman-generator 提供了一个基础的 generator,里面提供了一些功能,如文件写入。要创建的 generator 需要继承这个 generator 并导出。

              const Generator = require('yeoman-generator');
              module.exports = class extends Generator{}
              
            2. Yeoman Generator在工作时会自动执行导出的类中的方法

            3. 在生命周期方法中调用父类提供的一些方法实现我们的功能,如文件写入。

              write() {
                  // generator 写入文件的时候会调用 writer 方法
                  // 这里通过 this. 的方式调用父类的一些方法
                  // this.fs.write(
                  //     this.destinationPath('test.txt'),
                  //     Math.random().toString()
                  // );
                  // 模板文件路径
                  const temp = this.templatePath('temp.html');
                  // 目标文件路径
                  const dest = this.destinationPath('test.html');
                  // 模板数据上下文
                  const content = this.answers;
                  this.fs.copyTpl(temp, dest, content);
              }
              
            4. 测试:运行 yarn link 将编写的generator link 到全局,然后通过 yo generatorName 来运行编写的generator

          5. 使用模板创建文件

            1. 创建并编写模板文件。模板文件支持 EJS 语法
            2. 调用父类的 fs.copyTpl 方法来写入模板文件内容到目标文件
              // 模板文件路径
              const temp = this.templatePath('temp.html');
              // 目标文件路径
              const dest = this.destinationPath('test.html');
              // 模板数据上下文
              const content = this.answers;
              this.fs.copyTpl(temp, dest, content);
              
          6. 接收用户输入:通过父类的prompt方法来实现

            1. 定义 prompting(){} 方法。generator 在询问用户环节时会自动调用此方法
            2. 在方法中调用父类的prompt()方法并返回发出对用户的命令行询问
              1. prompt 方法 返回一个 promise

              2. 使用 then 方法接收用户输入或将 prompting 方法定义为 async 方法,并使用 await 控制异步流程。

                async prompting() {
                    this.answers = await this.prompt([
                        {
                            type: 'input',
                            name: 'name',
                            message: 'Your project name',
                            default: this.appname, // 项目名称。
                        },
                        {
                            type: 'confirm',
                            name: 'cool',
                            message: 'Would you link to enable the Cool feature?'
                        }
                    ]);
                }
                
              3. 然后在其他方法中使用获取的answer

                write() {
                    // generator 写入文件的时候会调用 writer 方法
                    // 这里通过 this. 的方式调用父类的一些方法
                    // this.fs.write(
                    //     this.destinationPath('test.txt'),
                    //     Math.random().toString()
                    // );
                    // 模板文件路径
                    const temp = this.templatePath('temp.html');
                    // 目标文件路径
                    const dest = this.destinationPath('test.html');
                    // 模板数据上下文
                    const content = this.answers;
                    this.fs.copyTpl(temp, dest, content);
                }
                
        3. 自定义 vue Generator 来生成 Vue 的项目的步骤:

          1. 准备一个基本的项目文件
          2. 将项目文件中的可变项提取出来,并使用 EJS 等模板语法处理
          3. 将处理后的文件挨个写入到目标文件。
        4. 发布自定义的Generator

          1. git 提交 Generator 项目到远端仓库
          2. 安装 publish
          3. yarn/npm publish --registry=https://registry.yarnpkg.com 发布到远端仓库。后面添加 --registry 是因为本地是淘宝镜像。
          4. 如果需要同步到 Yeoman作为一个SubGenerator, 关键字需要添加 yeoman-generator
      4. Plop的使用:创建同类型文件的工具
        1. 基本使用
          1. 在项目中安装plop, npm i plop -d
          2. 创建 plopfile.js 文件,并编写 plop generator
            1. plopfile.js 文件导出一个方法,该方法接收一个 plop 对象,包含 plop.js 提供的所有方法

            2. 在方法中调用 plop.setGenerator 来定义一个生成器,可以多次调用来定义多个生成器

              1. setGenerator(name, option): 其中 name 是生成器的名字,option 是生成器的配置选项,用来定义用户询问和活动
              2. option 包含3个属性
                1. description:生成器的描述信息

                2. prompts: [],用户交互问询配置对象

                  prompts: [
                      {
                          type: 'input', // 用户输入类型
                          name: 'name', // answer 接收时对应的字段
                          message: 'Generator name?' // 用户交互时命令行提示信息
                      }
                  ],
                  
                3. actions: [],定义 generator 的活动

                  actions: [
                      {
                          type: 'add', // 动作类型, add 表示添加文件
                          path: 'generators/{{name}}/index.js', // 生成文件的路径
                          templateFile: 'template.hbs' // 模板文件路径
                      }
                  ]
                  
            3. 运行plop的方式

              1. 使用 npm 运行的步骤

                1. package.jsonscript 中添加命令行

                  "scripts": {
                      "plop": "plop"
                    },
                  
                2. 运行命令行 npm run plop generatorName
                  在这里插入图片描述

              2. 使用 yarn 运行: 直接运行 yarn plop generatorName
                在这里插入图片描述

    4. 创建脚手架:即创建一个 node cli应用
      1. node cli 文件的头部声明 #! /usr/bin/env nodeUnixLinux 系统中的头部声明注释,告诉系统文件的执行环境为node,并且在告诉系统在系统的环境变量中查找 node 应用程序的位置。

      2. 通过命令行交互询问用户问题, 使用 inquire 发起命令行交互

      3. 根据用户回答结果生成文件

        inquirer.prompt([
            {
                type: 'input',
                name: 'name',
                message: '请输入项目名称:'
            }
        ]).then(answers => { // 获取用户输入的问题答案。根据答案生成文件
            // console.log(answers)
            // 根据 path 获取模板文件所在位置
            const tmp = path.resolve(__dirname, '../templates');
            // 目标文件位置,即命令行当前位置
            const dir = process.cwd();
            // fs文件系统读取文件
            fs.readdir(tmp, (err, files) => {
                if(err) throw err;
                console.log(files.length)
                files.forEach(f => { // 遍历读取到的文件,并使用ejs模板解析
                    ejs.renderFile(path.join(tmp, f), answers, (err, result) => {
                        if (err) throw err;
                        // 将结果写入到目标目录
                        fs.writeFileSync(path.join(dir, f), result);
                    })
                })
            })
        })
        
  3. 前端自动化构建

    1. 自动化构建:自动将源代码转换成生产代码。开发阶段使用提高效率的语法、规范和标准(es, sass, 模板引擎)浏览器不能直接支持,需要通过转换。
    2. 自动化构建的工作流
      1. 按项目结构创建项目目录
        在这里插入图片描述
      2. 添加 sass 支持: npm i sass -s;
        1. 添加 sass 之后可以在命令行使用命令: .\node_modules\.bin\sass scss\main.scss css\mian.csssass 文件编译成 css 文件
        2. npm scripts 中添加 "build": "sass scss/main.scss css/main.css --watch", 这样就可以在命令行运行 npm run build 来编译文件,--watch 表示监听文件变化,sass 文件变化时会触发编译
      3. 安装 browser-sync 模块在浏览器预览页面效果,npm i browser-sync -s;
        1. npm scripts 中添加 "server": "browser-sync . --files css/*.css", 之后可以在命令行运行 npm run server 命令,在浏览器打开页面 --files css/*.css 表示监听文件变化并同步到浏览器
      4. 由于 --watch 会阻塞命令的继续运行,需要有一种方式可以同步运行两个命令行,故此引入 npm-run-all 模块, npm i npm-run-all -d;
        1. npm scripts 中添加 "start": "run-p build server" 同时运行 buildserver 两个命令
        2. 命令行运行 npm start 即可启动自动化构建工作
    3. 常用的自动化构建工具
      1. Grunt:基于临时文件,构建速度慢,插件丰富

        1. grunt 的基本使用 grunt 代码默认支持同步模式
          1. 安装 grunt: npm i grunt -D,开发依赖
          2. 新建 gruntfile.js,编写 grunt 代码
            1. gruntfile.js 需要导出一个接收 grunt 参数的函数,在函数中注册任务

              1. 任务注册:调用 grunt.registerTask() 方法注册任务。方法接收两个参数:

                1. 第一个参数为 String 类型,表示任务的名称
                2. 第二个参数可以是 一个函数 或者 任务名称数组
                  1. 参数为一个函数时,运行命令行 grunt taskName 时将会调用函数

                    grunt.registerTask('foo', () => {
                      console.log(grunt.config('foo.bar'));
                    })
                    
                  2. 参数为 任务名称数组 时,运行命令行 grunt taskName 时会依次执行对应的任务

                    grunt.registerTask('default', ['foo', 'bar']);
                    
              2. 运行命令行 grunt taskName 时如果省略 taskName 会执行名为 default 的任务。

                grunt.registerTask('default', ['foo', 'bar']);
                
              3. 异步任务的注册: grunt 代码默认支持同步模式, 所以异步任务需要现在回调函数中标识这是个异步任务

                1. 函数中调用 this.async() 标识这个是异步任务,该函数返回一个 done 函数。

                2. 需要在异步任务执行完毕之后调用 done 函数,标识任务结束。

                  grunt.registerTask('task', function() {
                          const done = this.async();
                          setTimeout(() => {
                              console.log('async hello');
                              done(false);
                          }, 3000);
                      });
                  
            2. 任务的运行通过命令行运行 grunt task

              1. npm: 需要先在 package.jsonscripts 中配置命令 "grunt": "grunt" , 然后运行 npm run grunt

                "scripts": {
                    "grunt": "grunt"
                  },
                
              2. yarn: 直接运行命令 yarn grunt taskName

            3. 任务失败的标记

              1. 非异步任务的失败,只需要在回调函数中 return false;

                grunt.registerTask('foo', () => {
                   console.log(grunt.config('foo.bar'));
                    return false;
                })
                
              2. 任务失败则后续的任务都不会执行

              3. 异步任务的失败则需要调用 done(false); 时传入 false。

                grunt.registerTask('task', function() {
                    const done = this.async();
                    setTimeout(() => {
                        console.log('async hello');
                        done(false);
                    }, 3000);
                });
                
            4. 任务的配置方法:

              1. 通过 grunt.initConfig() 方法来初始化配置。方法接收一个对象。

                grunt.initConfig({
                    "multi-target": {
                        options: {
                            bar: 123
                        },
                        css: {
                            options: {
                                bar: 'css-123',
                                foo: '123'
                            }
                        },
                        js: 'js-21'
                    },
                    "clean": {
                        temp: 'tem/app.js',
                        all: 'tem/**'
                    }
                })
                
              2. 配置对象的属性值的获取有两种方式:

                grunt.config('foo').bar
                grunt.config('foo.bar')
                
            5. 多目标任务:

              1. 多目标任务的注册:grunt.registerMultiTask('multi-target', function() {})

              2. 多目标任务注册时需要在 grunt.initConfig() 的配置中指定一个和任务同名的属性。该属性的值必须为对象形式,值对象中每一个属性即为目标(options除外),属性名为目标名称

                grunt.initConfig({
                    "multi-target": {
                        options: {
                            bar: 123
                        },
                        css: 'css-123',
                        js: 'js-21'
                    },
                })
                
              3. 多目标任务的回调函数会根据配置中的目标个数多次执行,在回调方法中可以通过 this.target 来获取目标名称,this.data 来获取目标数据。

                // 多目标任务注册
                grunt.registerMultiTask('multi-target', function() {
                    console.log(this.target, this.data);
                });
                

                在这里插入图片描述

              4. 配置对象中的 options 属性作为多目标任务的配置属性,在任务注册回调中通过 this.options() 方法来访问配置属性

                grunt.initConfig({
                        "multi-target": {
                            options: {
                                bar: 123
                            },
                            css: 'css-123',
                            js: 'js-21'
                        },
                    })
                
                    // 多目标任务注册
                    grunt.registerMultiTask('multi-target', function() {
                        console.log(this.options());
                        console.log(this.target, this.data);
                    });
                

                在这里插入图片描述

              5. 目标属性的 options 属性会覆盖多目标任务配置中的 options 属性。

                grunt.initConfig({
                        "multi-target": {
                            options: {
                                bar: 123
                            },
                            css: {
                                options: {
                                    bar: 'css-123',
                                    foo: '123'
                                }
                            },
                            js: 'js-21'
                        },
                        "clean": {
                            temp: 'tem/app.js',
                            all: 'tem/**'
                        }
                    })
                
                    // 多目标任务注册
                    grunt.registerMultiTask('multi-target', function() {
                        console.log(this.options());
                        console.log(this.target, this.data);
                    });
                

                在这里插入图片描述

              6. 目标任务的运行:在任务后面加 “:” 和 目标名称。
                在这里插入图片描述

            6. 插件的使用:插件是 grunt 的核心

              1. 插件的命名规则:grunt插件的命名统一以 grunt- 加上插件名称的形式。例如grunt-pluginName
              2. 插件的使用:以 grunt-contrib-clean(文件清除插件) 为例
                1. 安装插件:npm i grunt-contrib-clean

                2. 加载插件任务:grunt.loadNpmTasks('grunt-contrib-clean');

                3. 配置插件任务:grunt.initConfig({ pluginName: {} })

                  grunt.initConfig({
                     "clean": {
                           temp: 'tem/app.js',
                           all: 'tem/**'
                       }
                   })
                  
                   // 加载插件中的任务
                   grunt.loadNpmTasks('grunt-contrib-clean');
                  
            7. 常用插件:

              1. load-grunt-tasks: 自动加载所有的 grunt 插件

                const loadGruntTasks = require('load-grunt-tasks')
                // 自动加载grunt插件
                loadGruntTasks(grunt)
                
              2. grunt-sass: 在files中配置编译的目的文件和源文件

                sass: {
                    options: {
                        sourceMap: true,
                        implementation: sass
                    },
                    main: {
                        files: {
                            'dist/css/app.css': 'scss/app.scss'
                        }
                    }
                }
                
              3. grunt-babel: 在files中配置编译的目的文件和源文件和 grunt-sass 类似

                babel: {
                   options: {
                        sourceMap: true,
                        presets: ['@babel/preset-env']
                    },
                    main: {
                        files: {
                            'dist/js/app.js': 'src/app.js'
                        }
                    }
                },
                
              4. grunt-contrib-watch: 监听文件变化,自动执行任务。通过 files 指定监听的文件,task 指定文件变化之后执行的任务。

                watch: {
                     js: {
                         files: ['src/*.js'],
                         task: ['babel']
                     },
                     css: {
                         files: ['scss/*.scss'],
                         task: ['sass']
                     }
                 },
                
      2. Gulp:基于内存,速度快,可以同时执行多个任务,插件丰富

        1. 基本使用:gulp 的函数默认都是异步的

          1. 环境监测:node 8.11+ npm 5.6+ npx 9.7+
          2. 安装 gulp
          3. 新建 gulpfile.js
          4. 编写 gulpfile.js
            1. 任务的注册:

              1. 通过 exports.taskName 导出任务

                exports.default = series(foo, bar, wsq)
                
              2. 通过 task 注册任务

                task('fff', done => {
                    console.log('ffff');
                    done();
                })
                
            2. 任务的串行和并行

              const { series, parallel, task } = require('gulp');	
              
              1. 串行:任务依次执行,使用series方法实现

                series(foo, bar, wsq)
                
              2. 并行:多个任务同时执行,使用parallel方法实现

                parallel(foo, bar, wsq)
                
            3. 异步任务的实现方式

              1. 通过回调函数形式:回调中需要调用传入的 done 函数标志任务结束。

                1. 成功执行的任务

                  exports.callback = done => {
                      setTimeout(() => {
                          console.log('callback task');
                          done();
                      }, 1000);
                  }
                  
                2. 标记任务错误,需要在 done 函数中传入一个错误对象

                  exports.callback_error = done => {
                      setTimeout(() => {
                          console.log('callback error task');
                          done(new Error('task error'));
                      }, 1000);
                  }
                  
              2. 通过promise实现

                1. 成功执行的任务,返回一个resolve状态的promise

                  exports.promise = () => {
                      console.log('promise task');
                      return Promise.resolve();
                  }
                  
                2. 标记任务错误,返回一个reject状态的promise

                  exports.promise_error = () => {
                      console.log('promise error task');
                      return Promise.reject(new Error('error promise task'));
                  }
                  
              3. 通过async/await实现

                1. 成功执行的任务,await 一个resolve状态的promise

                  const timeoutPromise = (time) => {
                      return new Promise((resolve, reject) => {
                          setTimeout(resolve, time);
                      })
                  }
                  exports.async = async () => {
                      await timeoutPromise(2000)
                      console.log('async task');
                  }
                  
                2. 标记任务错误,await 一个reject状态的promise

                  const timeoutPromise = (time) => {
                      return new Promise((resolve, reject) => {
                          setTimeout(() => {
                            reject(new Error('error async task'))
                          }, time);
                      })
                  }
                  exports.async = async () => {
                      await timeoutPromise(2000)
                      console.log('async task');
                  }
                  
              4. 通过 stream 流的形式

                1. 通过流的状态来标志任务是否完成

                  // stream
                  exports.stream = () => {
                      const readStream = fs.createReadStream('package.json');
                      const writeStream = fs.createWriteStream('temp.txt');
                      readStream.pipe(writeStream);
                      return readStream;
                  }
                  
                2. 其本质是通过监听 streamend 状态来确定任务是否完成。

                  exports.stream = done => {
                      const readStream = fs.createReadStream('package.json');
                      const writeStream = fs.createWriteStream('temp.txt');
                      readStream.pipe(writeStream);
                      readStream.on('end', () => {
                          done()
                      })
                  }
                  
            4. 错误优先:gulp 是错误优先的,一旦发生错误,接下来的任务就不会执行。

        2. gulp 构建过程

          1. 读取文件:通过 fs.createReadStream(filePath);

          2. 定义转换过程:callback 接收两个参数,一个错误对象和转换完的输出数据,没有错误则传入 null

            	// 定义文件的转换
              const transform = new Transform({
                   transform: (chunk, encoding, callback) => {
                       const input = chunk.toString();
                       // 将输入转换为输出,去空格去注释
                       const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\\/g, '')
                       callback(null, output)
                   }
               })
            
          3. 输出到指定文件:通过 fs.createWriteStream('destFilePath')

            exports.default = () => {
                // 定义文件读取流
                const read = fs.createReadStream('main.css');
                // 指定文件输出
                const white = fs.createWriteStream('main.min.css');
                // 定义文件的转换
                const transform = new Transform({
                    transform: (chunk, encoding, callback) => {
                        const input = chunk.toString();
                        // 将输入转换为输出,去空格去注释
                        const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\\/g, '')
                        callback(null, output)
                    }
                })
                read
                .pipe(transform)
                .pipe(white);
                return read;
            }
            
        3. gulp 文件操作,gulp提供了两个用于文件操作的函数,srcdest

          1. src(path): 定义文件输入流,支持通配符
          2. dest(path): 定义文件输入出流,接收一个目录作为参数
          3. 管道:文件流通过管道流向下一个处环境,通过pipe方法来表示
          const { src, dest } = require('gulp');
          const cleanCss = require('gulp-clean-css');
          const rename = require('gulp-rename');
          
          exports.default = () => {
              return src('*.css')
              .pipe(cleanCss())
              .pipe(rename({ extname: '.min.css'}))
              .pipe(dest('dist'))
          }
          
        4. 一个完整的 gulp 构建流程

          1. 编译 less/sass 文件:通过 gulp-less,安装 less / sass

            1. gulp-sass 处理文件流时会自动忽略 “_” 开头的文件
          2. 编译 js 文件, 通过 gulp-babel,并安装 babel 相关的依赖 babel, @babel/core @babel/preset-env

          3. 编译 html 模板文件:通过 gulp-swigswig 是一种模板语法,也可以使用其他模板语法入 ejs

            1. swig 模板有缓存机制,在监听页面变化时可能导致看不到最新的结果。通过设置 cache 属性来关闭。此外可以通过data配置属性将数据传入模板。

              const page = () => {
                return src('src/**/*.html')
                     .pipe(plugins.swig({ 
                         defaults: {
                             cache: false
                         },
                         data,
                     }))
                     .pipe(dest('dist'))
                     .pipe(bs.reload({ stream: true }))
              }
              
          4. 压缩 img 文件:通过 gulp-imagemin, 使用 npm 安装失败时使用 cnpm 安装。

            // 复制图片
            const image = () => {
                return src('src/assets/**', { base: 'src' })
                    .pipe(plugins.imagemin())
                    .pipe(dest('dist'))
                    .pipe(bs.reload({ stream: true }))
            }
            
          5. 自动导入 gulp 插件: 通过 gulp-load-plugins 。 调用插件方法得到插件对象,然后使用 对象. 的方式来使用其他插件。插件名即为属性名。

            const loadPlugins = require('gulp-load-plugins');
            const plugins = loadPlugins();
            // 编译css文件
            const css = () => {
               return src('src/**/*.less')
                   .pipe(plugins.less())
                   .pipe(dest('temp'))
            }
            
          6. 编译之前清理文件,通过 delnpm i del -D

            // 文件清理
            const clean = () => {
                return del(['dist', 'temp']);
            }
            
          7. 开发服务器搭建: 使用 browser-sync 来搭建

            1. 安装 browser-syncnpm i browser-sync -D

            2. 导入并创建实例

              const bs = require('browser-sync').create();
              
            3. 通过 init 初始化服务器配置并启动

              bs.init({
                  // files: 'dist/**', // 可以用 bs.reload 来替代
                   port: 8099,
                   server: {
                       // baseDir: 'dist',
                       baseDir: ['temp', 'src'],
                       routes: {
                           './node_modules': 'node_modules'
                       }
                   }
               })
              
              1. baseDir:可以是字符串,也可以是字符数组。如果是数组,构建时查找资源文件如果没有从dist目录中找到,则会依次在后面的目录中去请求资源文件,直到找到为止。
              2. routes:指定相对路径到源文件的映射。即./node_modules下的资源请求都到根目录下的node_modules 目录下请求
            4. 监听文件变化及优化

              1. 监听文件变化通过 gulpwatch 方法来实现,watch方法接收两个参数,第一个参数是要监听的文件路径,第二个参数是文件变化之后执行的任务

                // 监听文件变化
                watch('src/**/*.less', css);
                watch('src/**/*.js', js);
                watch('src/**/*.html', page);
                watch('src/assets/**', bs.reload);
                

                也可以通过在任务最后使用.pipe(bs.reload({stream: true}))指定在文件变化之后刷新页面

                // 编译css文件
                const css = () => {
                    return src('src/**/*.less')
                        .pipe(plugins.less())
                        .pipe(dest('temp'))
                        .pipe(bs.reload({stream:true}))
                }
                
              2. web服务优化:图片和字体等文件在开发时不需要经过特殊处理,故此可以直接引用源文件,通过 baseDir: ['dist', 'src'] 进行指定。减少构建次数。图片和字体文件变化是可以通过 bs.reload 来刷新浏览器

                watch('src/assets/**', bs.reload);
                
          8. 项目中的 node_modules 模块的生产代码构建:

            1. node_modules 中的文件打包到一个指定文件中,然后再模板 html 中引入。
            2. 使用 gulp-useref 插件:需要在模板进过模板语法处理之后再处理
              1. 安装依赖 npm i gulp-useref -D

              2. gulpfile.js 中注册任务,并使用插件处理输出流

                return src('src/*.html')
                        .pipe(plugins.useref({ searchPath: ['temp', '.'] })) // 指定要处理文件的目录, '.'是为了去查找从 node_modules 中引入的模块。
                        .pipe(dest('dist'))
                
              3. html 模板中编写 构建注释,useref 会将构建注释中引入的资源提取出来放到指定的目标文件中。

                <!-- build:css css/antd.css -->
                <link rel="stylesheet" href="/node_modules/antd/dist/antd.dark.css">
                <link rel="stylesheet" href="/node_modules/antd/dist/antd.min.css">
                <!-- endbuild -->
                
                1. 在开始标签<!-- build:js js/bounds.js -->中指定构建之后输出的目标文件
                2. <!-- endbuild --> 标志结束。
          9. 对 html 中引入的文件进行压缩:html, css, js 需要在 useref 插件处理之后再进行压缩,并输出最终的代码

            1. 使用 gulp-if 对文件进行判断,在使用相应的插件进行压缩处理
            2. 压缩 html 文件,使用 gulp-htmlmin 插件
              1. htmlmin 插件默认只清除属性中多余的空格,如果需要去除其他内容需要指定属性

                .pipe(plugins.if(/\.html$/, plugins.htmlmin({ // htmlmin 插件默认只清除属性中多余的空格,如果需要去除其他内容需要指定属性
                     collapseWhitespace: true, // 删除标签之间的空白字符
                     minifyCSS: true, // 压缩css
                     minifyJS: true, // 压缩js
                     removeComments: true, // 去除注释
                 })))
                
            3. 压缩 js 文件, 使用 gulp-uglify 插件
              .pipe(plugins.if(/\.js$/, plugins.uglify()))
              
            4. 压缩 css 文件, 使用 gulp-clean-css 插件
              .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
              
              // 处理 node_modules 中的安装依赖打包
              const useref = () => {
                  return src('src/*.html')
                      .pipe(plugins.useref({ searchPath: ['dist', '.'] })) // 指定要处理文件的目录, '.'是为了去查找从 node_modules 中引入的模块。
                      .pipe(plugins.if(/\.html$/, plugins.htmlmin({ // htmlmin 插件默认只清除属性中多余的空格,如果需要去除其他内容需要指定属性
                          collapseWhitespace: true, // 删除标签之间的空白字符
                          minifyCSS: true, // 压缩css
                          minifyJS: true, // 压缩js
                          removeComments: true, // 去除注释
                      })))
                      .pipe(plugins.if(/\.js$/, plugins.uglify()))
                      .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
                      .pipe(dest('dist'))
              }
              
          10. 构建流程梳理:useref 对文件处理的前提是html, css, js这些文件都经过了lessbabelswig 等编译之后。如果 useref 处理文件时从 dist 文件读取然后写入到 dist 文件会存在读写冲突。所以需要一个临时文件存储 lessbabelswig 编译之后的文件,然后 useref 再从临时文件中读取,写入到 dist 文件。

            1. 修改 lessbabelswig 等编译之后的文件目录 dist 改为临时目录 temp

              // 编译css文件
              const css = () => {
                  return src('src/**/*.less')
                      .pipe(plugins.less())
                      .pipe(dest('temp'))
                      .pipe(bs.reload({stream:true}))
              }
              
              // 编译 js 文件
              const js = () => {
                  return src('src/**/*.js')
                      .pipe(plugins.babel({
                          presets: ['@babel/preset-react', '@babel/preset-env'],
                          plugins: ['@babel/plugin-proposal-class-properties']
                      }))
                      .pipe(dest('temp'))
              }
              
            2. browser-sync 插件 init 时的文件位置也需要修改

              // 处理 node_modules 中的安装依赖打包
              const useref = () => {
                  return src('src/*.html')
                      .pipe(plugins.useref({ searchPath: ['temp', '.'] })) // 指定要处理文件的目录, '.'是为了去查找从 node_modules 中引入的模块。
                      .pipe(plugins.if(/\.html$/, plugins.htmlmin({ // htmlmin 插件默认只清除属性中多余的空格,如果需要去除其他内容需要指定属性
                          collapseWhitespace: true, // 删除标签之间的空白字符
                          minifyCSS: true, // 压缩css
                          minifyJS: true, // 压缩js
                          removeComments: true, // 去除注释
                      })))
                      .pipe(plugins.if(/\.js$/, plugins.uglify()))
                      .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
                      .pipe(dest('dist'))
              }
              
              const server = () => {
                  // 监听文件变化
                  watch('src/**/*.less', css);
                  watch('src/**/*.js', js);
                  watch('src/**/*.html', page);
                  watch('src/assets/**', bs.reload);
              
                  bs.init({
                      // files: 'dist/**', // 可以用 bs.reload 来替代
                      port: 8099,
                      server: {
                          // baseDir: 'dist',
                          baseDir: ['temp', 'src'],
                          routes: {
                              './node_modules': 'node_modules'
                          }
                      }
                  })
              }
              
      3. FIS:百度推出构建工具,适合初学者使用,使用简单,功能全面,包括自动化工具、性能优化、模块化框架、开发规范、代码部署等。 FIS高度集成,将常用的任务内置在FIS中。资源定位使其核心能力,编译时将相对路径转化为绝对文件。

最后,要将经常使用的工作流提取出来,作为一个公共的工作流,然后引入到各个项目中,这样就不用每当工作流需要调整的时候,挨个项目去调整。

在开发好的工作流的基础上对工作流进行封装的开发步骤:

  1. 公共工作流的提取:新建空白的node工程,在lib目录下新建index.js文件,并将开发的工作流中的gulpfile.js内容复制到index.js中,并将devDependencies中的内容拷贝到node工程下的dependencies中 。然后运行yarn/npm install 安装依赖。在此过程中有几点需要注意:

    1. package.json中需要通过main字段来指定文件入口为lib/index.js

    2. 需要在bin目录中新建index.js,并在package.json中指定bin字段来配置cli命令,并以node模块名为cli命令名。

      "main": "lib/index.js",
      "bin": {
        "wsq-page": "bin/wsq-page.js"
      },
      
  2. 引用公共工作流:现在node工程目录中,通过npm link/yarn linknode模块发布到本地的全局环境。然后再应用项目目录通过yarn link moduleName 来引用先前link发布的模块。并在应用项目的gulpfile.js中直接导出引用模块的内容。

    module.exports = require('wsq-page')
    
  3. 在应用项目的package.json中配置scripts来使用gulp工作流

    "scripts": {
      "compile": "gulp compile",
      "clean": "gulp clean",
      "lint": "gulp lint",
      "serve": "gulp serve",
      "build": "gulp build",
      "start": "gulp start",
      "deploy": "gulp deploy"
    }
    
  4. 提取可变变量到配置文件。对于一些文件路径每个项目可能都不尽相同,因此可以将这些文件路径作为可变配置,以便应用项目进行不同的配置,约定应用项目的根目录可以提供与一个page.config.js文件来覆盖默认的配置。配置文件需要导出一个对象,下面是一个配置文件的示例,配置文件可以根据工作流的实际情况进行设计。

    module.exports = {
      dist: 'build',
      temp: 'tp',
      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()
      },
    }
    
  5. 去除应用项目中的gulpfile.js,工作流提供cli命令供应用项目直接使用。

    1. 通过分析 node_modules 中的gulp.cmd,可知运行 gulp 命令实际就是node环境下执行gulp\bin\gulp.js
      在这里插入图片描述

    2. gulpfile.js不是必须的,命令行运行gulp命令时可以手动指定gulpfile.js,对应用项目来说,gulpfile.js实际上都是指向公共工作流中的入口文件。
      在这里插入图片描述

    3. 如果通过命令行运行时指定gulpfile为公共工作流中的入口文件,工作目录将发生改变,变为入口文件所在的目录。因此需要通过--cwd 来讲目录指定到当前命令运行的目录。
      在这里插入图片描述

    4. 综合上述信息我们可以在公共工作流中提供一个cli命令来模拟命令行执行,这样在应用项目中就可以通过命令行来运行工作流中的构建任务,而不必在每个应用项目中添加相同的gulpfile.js文件。

      "scripts": {
        "compile": "wsq-page compile",
         "clean": "wsq-page clean",
         "lint": "wsq-page lint",
         "serve": "wsq-page serve",
         "build": "wsq-page build",
         "start": "wsq-page start",
         "deploy": "wsq-page deploy"
       },
      
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值