开发脚手架及封装自动化构建工作流

主要解决的问题

  • 传统语言或语法的弊端
  • 无法使用模块化 / 组件化
  • 重复的机械工作
  • 代码风格统一/质量保证
  • 依赖后端服务接口支持
  • 整体依赖后端项目

工程化表现

  • 一切以提高效率、降低成本、质量保证为目的的手段都属于[工程化]
  • 一切重复的工作都应该被自动化

脚手架工具

常用的脚手架工具

  • React.js 项目 ----> create-react-app
  • Vue.js 项目 ----> vue-cli
  • Angular 项目 ----> angular-cli
  • Yeoman
  • Plop

Yeoman

  • The web’s scaffloding tool for modern webapps

Yeoman - 基本使用

  • 在全局范围安装 yo
     $ npm install yo -- global  # or yarn global add yo
    
  • 安装对应的 generator
    $ npm install generator-node --global # or yarn global add generator-node
    
  • 通过 yo 运行 generator
     $ cd path/to/project-dir
     $ mkdir my-module
     $ yo node
    

yeoman - Sub Generator

yo node:cli
yarn link //全局使用
yarn 
my-module --help

常规使用步骤

  • 明确你的需求
  • 找到合适的 Generator
  • 全局范围安装找到的 Generator
  • 通过 yo 运行对应的 Generator
  • 通过命令行交互填写选项
  • 生成你所需要的项目结构

自定义 Generator

  • 基于 Yeoman 搭建自己的脚手架

创建 Generator模块

  • Generator 本质上就是一个 NPM 模块
    在这里插入图片描述
  • 名字必须为:generator-< name >

根据模板创建文件

  • 相对于手动创建每一个文件,模板的方式大大提高了效率

接收用户输入数据

prompting () {
    // Yeoman 在询问用户环节会自动调用此方法
    // 在此方法中可以调用父类的 prompt() 方法发出对用户的命令行询问
    return this.prompt([
      {
        type: 'input',
        name: 'name',
        message: 'Your project name',
        default: this.appname // appname 为项目生成目录名称
      }
    ])
    .then(answers => {
      // answers => { name: 'user input value' }
      this.answers = answers
    })
  }

发布 Generator

Plop

  • 一个小而美的脚手架工具

Plop 的具体使用

  • 将 plop 模块作为项目开发依赖安装
  • 在项目根目录下创建一个 plopfile.js 文件
  • 在 plopfile.js 文件中定义脚手架任务
  • 编写用于生成特定类型文件的模板
  • 通过 Plop 提供的 CLI 运行脚手架任务
    // 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'
          }
        ]
      })
    }
    

自动化构建

npm scripts : 实现自动化构建工作流的最简单方式

常用的自动化构建工具

  • Grunt 构建速度较慢,磁盘读写操作
  • Gulp 解决了Grunt 构建速度慢问题
  • FIS 百度前端推出 – 大而全

Grunt

  • 基本使用

      yarn init 
      yarn add grunt
    
  • 创建 gruntfile.js grunt 的入口函数

    // Grunt 的入口文件
    // 用于定义一些需要 Grunt 自动执行的任务
    // 需要导出一个函数
    // 此函数接收一个 grunt 的对象类型的形参
    // grunt 对象中提供一些创建任务时会用到的 API
    
    module.exports = grunt => {
      grunt.registerTask('foo', 'a sample task', () => {
        console.log('hello grunt')
      })
    
      grunt.registerTask('bar', () => {
        console.log('other task')
      })
    
      // // default 是默认任务名称
      // // 通过 grunt 执行时可以省略
      // grunt.registerTask('default', () => {
      //   console.log('default task')
      // })
    
      // 第二个参数可以指定此任务的映射任务,
      // 这样执行 default 就相当于执行对应的任务
      // 这里映射的任务会按顺序依次执行,不会同步执行
      grunt.registerTask('default', ['foo', 'bar'])
    
      // 也可以在任务函数中执行其他任务
      grunt.registerTask('run-other', () => {
        // foo 和 bar 会在当前任务执行完成过后自动依次执行
        grunt.task.run('foo', 'bar')
        console.log('current task runing~')
      })
    
      // 默认 grunt 采用同步模式编码
      // 如果需要异步可以使用 this.async() 方法创建回调函数
      // grunt.registerTask('async-task', () => {
      //   setTimeout(() => {
      //     console.log('async task working~')
      //   }, 1000)
      // })
    
      // 由于函数体中需要使用 this,所以这里不能使用箭头函数
      grunt.registerTask('async-task', function () {
        const done = this.async()
        setTimeout(() => {
          console.log('async task working~')
          done()
        }, 1000)
      })
    }
    
  • 标记任务失败

    module.exports = grunt => {
      // 任务函数执行过程中如果返回 false
      // 则意味着此任务执行失败
      grunt.registerTask('bad', () => {
        console.log('bad working~')
        return false
      })
    
      grunt.registerTask('foo', () => {
        console.log('foo working~')
      })
    
      grunt.registerTask('bar', () => {
        console.log('bar working~')
      })
    
      // 如果一个任务列表中的某个任务执行失败
      // 则后续任务默认不会运行
      // 除非 grunt 运行时指定 --force 参数强制执行
      grunt.registerTask('default', ['foo', 'bad', 'bar'])
    
      // 异步函数中标记当前任务执行失败的方式是为回调函数指定一个 false 的实参
      grunt.registerTask('bad-async', function () {
        const done = this.async()
        setTimeout(() => {
          console.log('async task working~')
          done(false)
        }, 1000)
      })
    }
    
  • Grunt 配置选项方法

     module.exports = grunt => {
      // grunt.initConfig() 用于为任务添加一些配置选项
      grunt.initConfig({
        // 键一般对应任务的名称
        // 值可以是任意类型的数据
        foo: {
          bar: 'baz'
        }
      })
    
      grunt.registerTask('foo', () => {
        // 任务中可以使用 grunt.config() 获取配置
        console.log(grunt.config('foo'))
        // 如果属性值是对象的话,config 中可以使用点的方式定位对象中属性的值
        console.log(grunt.config('foo.bar'))
      })
    }
    
  • Grunt 多目标任务

    module.exports = grunt => {
      // 多目标模式,可以让任务根据配置形成多个子任务
    
      // grunt.initConfig({
      //   build: {
      //     foo: 100,
      //     bar: '456'
      //   }
      // })
    
      // grunt.registerMultiTask('build', function () {
      //   console.log(`task: build, target: ${this.target}, data: ${this.data}`)
      // })
    
      grunt.initConfig({
        build: {
          options: {
            msg: 'task options'
          },
          foo: {
            options: {
              msg: 'foo target options'
            }
          },
          bar: '456'
        }
      })
    
      grunt.registerMultiTask('build', function () {
        console.log(this.options())
      })
    }
    
  • Grunt 插件的使用

    • npm安装插件
    • 使用 grunt.loadNpmTasks 加载插件
    • grunt.initConfig 配置插件选项
    module.exports = grunt => {
      grunt.initConfig({
        clean: {
          temp: 'temp/**'
        }
      })
      
      grunt.loadNpmTasks('grunt-contrib-clean')
    }
    
  • Grunt常用插件

    const sass = require('sass')
    const loadGruntTasks = require('load-grunt-tasks')
    
    module.exports = grunt => {
      grunt.initConfig({
        sass: {
          options: {
            sourceMap: true,
            implementation: sass
          },
          main: {
            files: {
              'dist/css/main.css': 'src/scss/main.scss'
            }
          }
        },
        babel: {
          options: {
            sourceMap: true,
            presets: ['@babel/preset-env']
          },
          main: {
            files: {
              'dist/js/app.js': 'src/js/app.js'
            }
          }
        },
        watch: {
          js: {
            files: ['src/js/*.js'],
            tasks: ['babel']
          },
          css: {
            files: ['src/scss/*.scss'],
            tasks: ['sass']
          }
        }
      })
    
      // grunt.loadNpmTasks('grunt-sass')
      loadGruntTasks(grunt) // 自动加载所有的 grunt 插件中的任务
    
      grunt.registerTask('default', ['sass', 'babel', 'watch'])
    }
    

Grunt常用插件总结:

  • css处理
    安装 scss处理: grunt-sass sass
    压缩:grunt-contrib-cssmin

    sass: {
    	options: {
    		implementation: sass
    	},
    	dist: {
    	  files: [{
    		expand: true,
    		cwd: 'src/assets/styles',
    		src: ['*.scss'],
    		dest: 'dist/assets/styles',
    		ext: '.css'
    	  }]
    	}
    },
    
  • js处理
    安装 Es6处理: @babel/core @babel/preset-env grunt-babel
    压缩:grunt-contrib-uglify

    babel:{
    	options: {
    	  // sourceMap: true,
    	  presets: ['@babel/preset-env']
    	},
    	dist: {
    	  files:[{
    		expand: true,
    		cwd: 'src/assets/scripts',
    		src: ['*.js'],
    		dest: 'dist/assets/scripts'
    	  }] 
    	}
    },
    
  • HTML处理 swig 模板
    安装编译插件: grunt-swigtemplates
    压缩: grunt-contrib-htmlmin

    swigtemplates: {
    	options: {
    	    defaultContext: data,
    	    templatesDir: 'src'
    	},
        production: {
    	    dest: 'dist',
    	    src: ['src/*.html']
     	}
    },
    
  • 图片压缩
    安装插件: grunt-contrib-imagemin

    imagemin: {
    	dist: {
    		options: {
    			optimizationLevel: 3 //定义 PNG 图片优化水平
    		},
    		files: [{
    			expand: true,
    			cwd: 'src/assets/images',  
    			src: ['**'], 
    			dest: 'dist/assets/images' 
    		},
    		{
    			expand: true,
    			cwd: 'src/assets/fonts', 
    			src: ['**'], 
    			dest: 'dist/assets/fonts'
    		}]
    	}
    },
    
  • 拷贝文件
    安装插件: grunt-contrib-copy

    copy: {
     main: {
        files: [{
    		expand: true,
    		cwd: 'public',
    	    src: ['**'], 
    		dest: 'dist', 
    		filter: 'isFile',
    	}],
      },
    },
    
  • 删除文件
    安装插件: grunt-contrib-clean

    clean: {
      build: ['dist']
    },
    
  • 启动开发服务器
    安装插件: grunt-browser-sync

    browserSync: {
        bsFiles: {
            src : ['dist', 'src', 'public']
        },
        options: {
    		watchTask: true,
            server: {
                baseDir: ['dist', 'src', 'public'],
                routes: {
                	'/node_modules': 'node_modules'
                }
            }
        }
    },
    
  • 监听文件的变化
    安装插件: grunt-contrib-watch

    watch: {
    	js: {
    		files: ['src/assets/scripts/*.js'],
    		tasks: ['babel']
        },
        css: {
    		files: ['src/assets/styles/*.scss'],
    		tasks: ['sass']
    	}
    },
    
  • 同是执行多个任务

    安装插件: grunt-concurrent

    concurrent: {
    	target: {
    		tasks: ['browserSync', 'watch'],
    		options: {
    			logConcurrentOutput: true
    		}
    	}
    },
    
  • 处理页面中引用模块 URL 路径问题
    安装插件: grunt-useref grunt-css grunt-contrib-uglify grunt-contrib-concat

     useref: {
    	html: 'dist/*.html',
    	temp: 'dist'
    },
    

Gulp

  • 基本使用

     yarn init 
     yarn add gulp --dev
    
  • 创建 gulpfile.js gulp的入口函数

    exports.foo = done => {
    	console.log("foo ....")
    	done()
    }
    
  • 创建组合任务

    const { series, parallel } = require('gulp')
    
    const task1 = done => {
      setTimeout(() => {
        console.log('task1 working~')
        done()
      }, 1000)
    }
    
    const task2 = done => {
      setTimeout(() => {
        console.log('task2 working~')
        done()
      }, 1000)  
    }
    
    const task3 = done => {
      setTimeout(() => {
        console.log('task3 working~')
        done()
      }, 1000)  
    }
    
    // 让多个任务按照顺序依次执行
    exports.foo = series(task1, task2, task3)
    
    // 让多个任务同时执行
    exports.bar = parallel(task1, task2, task3)
    

构建过程核心工作原理

  • 输入 - 读取流
  • 加工 - 转换流
  • 输出 - 写入流
    const fs = require('fs')
    const { Transform } = require('stream')
    
    exports.default = () => {
      // 文件读取流
      const readStream = fs.createReadStream('normalize.css')
    
      // 文件写入流
      const writeStream = fs.createWriteStream('normalize.min.css')
    
      // 文件转换流
      const transformStream = new Transform({
        // 核心转换过程
        transform: (chunk, encoding, callback) => {
          const input = chunk.toString()
          const output = input.replace(/\s+/g, '').replace(/\/\*.+?\*\//g, '')
          callback(null, output)
        }
      })
    
      return readStream
        .pipe(transformStream) // 转换
        .pipe(writeStream) // 写入
    }
    

Gulp 文件操作 API + 插件的使用

```
// src 读取流 dest 写入流
const { src, dest } = require('gulp')
const cleanCSS = require('gulp-clean-css')
const rename = require('gulp-rename')

   .pipe(rename({ extname: '.min.css' }))
  exports.default = () => {
  return src('src/*.css')
    .pipe(cleanCSS())
   .pipe(dest('dist'))
}
```

gulp常用的插件

  • gulp-sass 将sass文件转换为 css
  • gulp-babel @babel/core @babel/preset-env 转换es6新特性
  • gulp-swig 转换HTML swig 模板
  • gulp-imagemin 压缩img
  • del 清除文件
  • gulp-load-plugins 自动加载 gulp 插件
  • browser-sync 启动一个开发服务器
    const serve = () => {
    	watch('src/assets/styles/*.scss',style)
    	watch('src/assets/scripts/*.js',script)
    	watch('src/*.html',page)
    	// watch('src/assets/images/**',image)
    	// watch('src/assets/fonts/**',font)
    	// watch('public/**',extra)
    	
    	bs.init({
    		notify: false,
    		port: 2080,
    		// open: false,
    		files: 'dist/**', // 监听 dist 文件变化刷新
    		server: {
    			baseDir: ['dist', 'src', 'public'],
    			routes: {
    				'/node_modules': 'node_modules'
    			}
    		}
    	})
    }
    
  • gulp-useref 根据构建注释构建css,js 路径
  • gulp-htmlmin 压缩 HTML 文件
  • gulp-uglify 压缩 JS 文件
  • gulp-clean-css 压缩 CSS 文件
  • gulp-if 判断文件类型
    const useref = () => {
    	return src('dist/*.html',{base: 'dist'})
    	.pipe(plugins.useref({searchPath: ['dist', '.']}))
    	.pipe(plugins.if(/\.js$/, plugins.uglify()))
    	.pipe(plugins.if(/\.css$/, plugins.cleanCss()))
    	.pipe(plugins.if(/\.html$/, plugins.htmlmin({
    		collapseWhitespace: true,
    		minifyCSS: true,
    		minifyJS: true
    	})))
    	.pipe(dest('release'))
    }
    

封装自动化构建工作流

  • 创建一个模块,将模块发布npm,新项目使用npm

  • 将gulpfile 中的代码替换到入口文件

  • 将 gulpfile 完成项目中的 package.json 依赖拷入新 package.json 项目依赖(dependencies)

  • 将模块 yarn link 到全局

  • 在项目中使用link的模块

  • 提取公共配置文件

    const cwd = process.cwd() //命令行工作
    let config = {
      // default config
      build: {
        src: 'src',
        dist: 'dist',
        temp: 'temp',
        public: 'public',
        paths: {
          styles: 'assets/styles/*.scss',
          scripts: 'assets/scripts/*.js',
          pages: '*.html',
          images: 'assets/images/**',
          fonts: 'assets/fonts/**'
        }
      }
    }
    
    try {
      const loadConfig = require(`${cwd}/pages.config.js`)
      config = Object.assign({}, config, loadConfig)
    } catch (e) {}
    
  • 包装 Gulp CLI
    创建 bin --> 项目名.js文件

    #!/usr/bin/env node
    
    process.argv.push('--cwd')
    process.argv.push(process.cwd())
    process.argv.push('--gulpfile')
    process.argv.push(require.resolve('..'))
    
    require('gulp/bin/gulp')
    
  • 发布
    在package.json files 中增加 “bin”
    yarn publish

  • 同步时间差问题:
    npm.taobao.org 中找到模块 点击 SYNC

FIS

  • 基本使用
    yarn global add fis3
    
  • 创建 fis-conf.js
     fis.match('*.{js,scss,png}', {
      release: '/assets/$0'
    })
    
    fis.match('**/*.scss', {
      rExt: '.css',
      parser: fis.plugin('node-sass'),
      optimizer: fis.plugin('clean-css')
    })
    
    fis.match('**/*.js', {
      parser: fis.plugin('babel-6.x'),
      optimizer: fis.plugin('uglify-js')
    })
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值