我是靠谱客的博主 眯眯眼草莓,最近开发中收集的这篇文章主要介绍拉勾前端高薪就业课程笔记第三弹(模块2-1),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

一、简述

模块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.binsass scssmain.scss cssmian.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环境下执行gulpbingulp.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"
       },
      

最后

以上就是眯眯眼草莓为你收集整理的拉勾前端高薪就业课程笔记第三弹(模块2-1)的全部内容,希望文章能够帮你解决拉勾前端高薪就业课程笔记第三弹(模块2-1)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(37)

评论列表共有 0 条评论

立即
投稿
返回
顶部