最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 开发脚手架及封装自动化构建工作流

    正文概述 掘金(螺旋静)   2020-11-24   582
    • 工程化:整体设计架构
    • 实现工程化出发
      1. 模块化
      2. 组件化
      3. 规范化
      4. 自动化
    • 解决了:
      1. 传统语言或语法的舞弊
      2. 大量手动重复、机械工作
      3. 代码不统一
      4. 依赖后端接口的支持

    脚手架工具

    作用

    • 创建项目基础结构、提供项目规范和约定

    • 相同的组织结构、开发范式、模块依赖、工具配置、项目的基础代码

      例:IDE创建项目的过程

    通用脚手架工具剖析

    React : create-react-app

    Vue.js : vue-cli

    Angular : angular-cli

    作用:根据信息创建对应的项目于基础结构

    Yeoman

    • 一个通用的脚手架工具

    • Yeoman本质上就是一个Node.js 的CLI程序

    • Yeoman需要配合特定的Generator模块才能够生成对应的项目骨架

    使用

    • 全局安装yo

      npm install yo --global
      
    • 安装对应的generator,要搭配特定的generator

      npm install generator-node --global
      
    • 通过yo运行generator

      cd path/to/project-dir
      mkdir my-module
      yo node
      

    Sub Generator

    • 用于补充生成项目中的文件

    • 添加cli结构

      yo mode:cli
      
    • 把模块链接到全局范围,使之成功一个全局模块包

      yarn link
      
    • 步骤

    1. 明确你的需求
    2. 找到合适的Generator
    3. 全局范围安装找的Generator
    4. 通过Yo运行对应的Generator
    5. 通过命令行交互填写选项
    6. 生成你所需要的项目结构

    开发脚手架 / 开发Generator

    自定义Generator

    • Generator本质上就是一个NPM模块
    • 一般通过Yeoman实现自定义脚手架实际上就是开发一个Generator

    步骤

    • 创建一个package.json

      yarn init
      
    • 安装一个提供工具函数的生成器模块

      yarn add yeoman-generator
      
    • 创建generators\app\index.js,作为Generator的核心入口,需要导出一个继承自Yeoman Generator的类型。Yeoman Generator在工作时会自动调用我们在此类型中定义的一些生命周期方法,我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能:文件写入

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

    根据模板创建文件

    • 新建一个模板文件,内部可以使用EJS模板标记输出数据,例如:<%= title%>,其它的EJS语法也支持
    • 这样就可以不借助于fs.write写入文件
    //通过模板方式写入文件到目标目录
    
    //模板文件路径
    const tmpl = this.templatePath('foo.txt')
    //输出目标路径
    const output = this.destinationPath('foo.txt')
    //模板数据上下文
    const context = { title:'hello' , success:false }
    //将模板文件自动映射到输出文件上
    this.fs.copyTpl(tmpl, output, context)
    
    • 作用:相对于手动创建每一个文件,模板的方式大大提高了效率

    动态接收用户输入

    module.exports = class extends Generator {
        prompting() {
            //Yeoman 在询问用户环境会自动调用此类方法
          	//在此方法中可以调用父类的prompt()方法发出对用户的命令行询问,它是一个promise方法
            return this.prompt([
                {
                    type: 'input',
                    name: 'name',
                    message: 'Your project name',
                    default: this.appanem//appname为项目生成目录名称
                }
            ])
            。then(answers => {
                //answers => {name:'user input value'}
                this.answers = answers
            })
        }
    }
    
    yo sample
    

    Vue Generator案例(略)

    发布Generator(略)

    淘宝npm镜像源是只读的,不能发布

    Plop

    集成到项目之中,创建同类型的项目文件,提高每次我们在项目创建文件时的效率

    • 一般是创建项目中的文件
    yarn add plop --dev
    yarn plop component
    

    示例:根据模板创建新的文件

    yarn add plop --dev
    

    定义脚手架任务:

    //./src/plopfile.js
    
    //plop入口文件,需要导出一个文件
    //函数接收一个plop对象,用于创建生成器任务
    module.exports = plop => {
        //生成器
        plop.setGenerator('component' , {
            description: 'create',
            //发起命令行交互询问
            prompts:[
                {
                    type: 'input',
                    name: 'name',
                    message: 'component name',
                    default: 'MyComponent'
                }
            ],
            //动作对象
            actions:[
                {
                    type:'add',//添加一个全新的文件
                    path:'src/components/{{name}}/{{name}}.js',
                    //模板文件
                    templateFile:'plop-templates/component.hbs'
                }
            ]
        })
    }
    

    模板文件书写:

    //.\plop-templates\component.hbs
    
    import React from 'react';
    
    export default () => (
      <div className="{{name}}">
        <h1>{{name}} Component</h1>
      </div>
    )
    

    命令行:

    yarn plop component
    

    总结

    1. 将plop模块作为项目开发依赖安装
    2. 在项目根目录下创建一个plopfile.js文件
    3. 在plopfile.js文件中定义脚手架任务
    4. 编写用于生成特定类型文件的模板
    5. 通过Plop提供的CLI运行脚手架任务

    脚手架工作原理(自定义一个小型脚手架)

    脚手架的工作过程:

    • 通过命令行交互询问用户问题

    • 根据用户回答的结果生成文件

    步骤:

    1. 创建一个package.json

      yarn init
      
    2. 安装项目所需要的依赖

      • inquirer:用户询问模块
      • ejs:模板引擎
      yarn add inquirer
      yarn add ejs
      
    3. 指定cli入口文件:通过package.json中的bin字段指定

      //./package.json
      {
          "bin": "cli.js"
      }
      
    4. cli.js

      #!/usr/bin/env node
      // Node CLI 应用入口文件必须要有这样的文件头
      // 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
      // 具体就是通过 chmod 755 cli.js 实现修改
         
      const fs = require('fs')
      const path = require('path')
      const inquirer = require('inquirer')
      const ejs = require('ejs')
         
      inquirer.prompt([//inquirer.prompt:发起一个命令行的询问,数组中每个命令行成员就是发起的一个问题
        {
          type: 'input',//问题输入方式
          name: 'name',//问题返回值的键值
          message: 'Project name?'//给用户的提示
        }
      ])
      .then(anwsers => {
        console.log(anwsers)//问题接收到的用户的答案
        // 根据用户回答的结果生成文件 
         
        // 模板目录
        const tmplDir = path.join(__dirname, 'templates')
        // 目标目录
        const destDir = process.cwd()// destDir是node的方法
         
          
        fs.readdir(tmplDir, (err, files) => { // readdir自动扫描tmplDir下的文件
          if (err) throw err
          files.forEach(file => {// file得到的是相对路径,通过模板引擎渲染文件
            ejs.renderFile(path.join(tmplDir, file), anwsers, (err, result) => {
              if (err) throw err
         
              // 将结果写入目标文件路径
                
       	console.log(result)
          fs.writeFileSync(path.join(destDir, file), result)
            })
          })
        })
      })
      
    5. 模板文件:

      • templates/index.html

        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title><%= name %></name></title>
            <!-- 通过<%=%>获得询问环境得到的用户询问答案 -->
        </head>
        <body>
        </body>
        </html>
        
      • templates/style.css

        body{
            height: auto;
        }
        
    6. 命令行创建一个空文件夹test进行测试

      mkdir test
      cd test
      define-scaffold
      

    自动化构建系统

    • 自动化构建工作流
    • 作用:使用提高效率的语法、规范和标准

    工具:

    • ECMAScript Next:提高编码效率和质量
    • Sass:提高可编程性
    • 模板引擎:抽象页面中重复的HTML

    自动化构建工具:构建转换那些不被支持的**“特性”**

    初体验

    graph LR
    A[你]--> |编写| B[scss]
    B[scss] --> |构建| C[css]
    C[css] --> |运行| D[浏览器]
    
    1. sass

      步骤:

    yarn add sass --dev
    .\node_modules\.bin\sass sass/main.scss css/style.css
    //输出路径 输入路径
    
    //也可以在package.json里添加字段
    scripts:{
    	"build": "sass sass/main.scss css/style.css"
    }
    
    yarn build
    
    1. NPM Scripts:实现自动化构建工作流最简方式

      yarn add browser-sync --dev
      //启动测试服务器
         
      //package.json
      scripts:{
      	"build": "sass sass/main.scss css/style.css --watch".
      	//“preserve”: "yarn build",
      	//完成启动web服务器之前自动构建sass文件
      	"serve": "browser-sync . --files \"css/*.css\“",
      	//监听最新的文件变化
      	"start": "run-p build serve"
      }
         
      yarn server
      //会自动启动一个web服务器,并浏览当前网页
         
      yarn add npm-run-all --dev
      //执行多个任务,以免堵塞
      yarn start
      //build任务和preserve任务同时被执行
      

    常用的自动化构建工具

    • Grunt 构建速度较慢,有磁盘读写操作
    • Gulp 基于内存实现,速度块,默认执行多个任务,生态完善
    • FIS 已集成

    Grunt

    1. default命名任务可以自动调用

    2. 默认支持同步模式,使用异步模式必须要使用this.async()得到一个回调函数

      module.exports = grunt => {
          grunt.registerTask('nihao', () => {
              console.log('hi')
          })
          //注册一个任务
         
          grunt.registerTask('xi', 'ren' => {
              console.log('hello')
          })
          //注册字符串
         
          grunt.registerTask('default', ['nihao','xi'])
         
          grunt.registerTask('async-task', function() {
              const done = this.async()
              setTimeout(() => {
                  console.log('hiiiii')
                  done()
              }, 1000)
          })
          //默认支持同步模式,使用异步模式必须要使用this.async()得到一个回调函数
      }
      
    3. 标记任务失败

      • return false
      • 异步 done(false)
    4. 配置任务:可以通过initConfig配置属性,任务通过config获取所设属性

          grunt.initConfig({
              name: {
                  a:'1'
              }
          })
          //配置属性
          grunt.registerTask('name', () => {
              console.log(grunt.config('name.a'))
              //1
          })
      
    5. 多任务模式:让任务根据配置形成多个任务

      	grunt.initConfig({
              build:{
                  options: {
                      hi:'3'
                  },
                  //作为任务的配置选项出现
                  css: {
                      options:{
                          css:'1'
                      }
                  },
                  js: '2'
              }
          })
         
          grunt.registerMultiTask('build', function(){
              console.log(this.options())
              console.log(`target: ${this.target}, data: ${this.data}`)
          })
          //同时执行两个目标 yarn grunt build:css执行指定目标
      
    6. Grunt插件

      yarn add grunt-contrib-clean
         
      //gruntfile.js
          //删除插件
          grunt.initConfig({
              clean: {
                  temp: 'temp/**'
              }
          })
         
          grunt.loadNpmTasks('grunt-contrib-clean')
      

    实现常见的构建任务

    1. 初始化项目 + 添加项目依赖
    yarn init
    yarn add grunt --dev
    
    yarn add grunt-sass sass --dev	//将css文件通过编译成sass
    yarn add grunt-babel @babel/core @babel/preset-env --dev	//使用babel转换特性
    yarn add grunt-contrib-watch --dev	//自动编译的监视文件
    yarn add grunt-contrib-clean 	//自动清除指定文件
    yarn add load-grunt-tasks --dev	//自动加载所有的插件
    
    1. 编写Grunt的入口文件:用于定义一些需要Grunt自动执行的任务,需要导出一个函数,接收一个grunt的参数
    /** gruntfile.js : 用来配置或定义任务(task)并加载Grunt插件
     * 1.wrapper函数
     * 2.项目与任务配置
     * 3.加载grunt插件和任务
     * 4.自定义任务
     **/
    
    const sass = require('sass')
    const loadGruntTasks = require('load-grunt-tasks')
    
    
    module.exports = grunt => { //wrapper函数
        grunt.initConfig({ //项目与任务配置
            sass: {
                options: {
                    sourceMap: true,//生成对应的sourceMap文件
                    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']
                },
                html: {
                    files: ['src/*.html'],
                    tasks: ['web_swig', 'bs-reload']
                }
            },
            clean: {
                //所要清除的文件路径
                files: 'dist/**'
            },
            //模板文件
            cptpl: {
                test: {
                    options: {
                        banner: '/*BANNER*/\n',
                        engine: 'dot'
                    },
                    files: {
                        'tmp/': ['test/html/abc.html']
                    }
                }
            }
        })
    
        loadGruntTasks(grunt)//自动加载所有的插件
    
        grunt.registerTask('default', ['clean', 'sass', 'babel', 'watch'])
        //为使用watch去监视sass和babel,使用registerTask做一个映射,启动时先编译,再做监听
    
    }
    
    1. 可以在package.json中对命令进行封装,方便使用

      {
          "scripts": {
          "clean": "grunt clean"
        },
      }
      
    2. 此时命令行运行,便可以自动构建文件

      yarn grunt
      

    Gulp

    1. 取消了同步代码模式,每个任务都必须是异步的任务,当任务结束之后需要调用回调函数或者其它方式

      exports.default = done => {
          console.log('2')
          done()
      }
      
    2. 创建组合任务:并行parallel、串行series

      const {
          series,
          parallel
      } = require('gulp')
         
      //导出成员的方式
      const hello = done => {
          setTimeout(() => {
              console.log('1')
              done() //标识任务完成
          }, 1000)
      }
      //取消了同步代码模式,每个任务都必须是异步的任务,当任务结束之后需要调用回调函数或者其它方式
         
      const hi = done => {
          setTimeout(() => {
              console.log('2')
              done()
          }, 1000)
      }
         
      const nihao = done => {
          setTimeout(() => {
              console.log('3')
              done()
          }, 1000)
      }
         
         
      exports.series = series(hello, hi, nihao)
      exports.parallel = parallel(hello, hi, nihao)
         
      //同时运行css和js的任务互不扰,可以使用并行
      //部署先编译,串行
      
      yarn glup hi
      
    3. 三种异步任务

      • callback
      • promise
      • async / await ES2017
      • stream
      exports.callback = done => {
          console.log('1')
          done()
      }
         
      exports.callback_error = done => {
          console.log('2')
          done(new Error('hi'))
      }
         
      ===========================================
         
      exports.promise = () => {
          console.log('3')
          return Promise.resolve()
      }
         
      exports.promise_error = () => {
          console.log('5')
          return Promise.reject(new Error('hi~'))
      }
         
      ===========================================
         
      //async / await
      const timeout = time => {
          return new Promise(resolve => {
              setTimeout(resolve, time)
          })
          //返回一个promise对象
      }
         
      exports.async = async () => {
          await timeout(1000)
          console.log('666')
      }
         
      ===========================================
         
      //Gulp支持
      exports.stream = () => {
          const readStream = fs.createRreadStream('package.json')
          const writeStream = fs.createRreadStream('temp.txt')
          readStream.pipe(writeStream)
          // return readStream//可以正常结束,glup中只是注册了这个事件,里面有done()
          readStream.on(`end` , () => {
              done()
          })
      }
      

    核心工作原理

    输入:读取流 => //src

    加工:转换流 => //dest

    输出:写入流

    const {src, dest} = require('gulp')
    const cleanCss = require('gulp-clean-css')//插件
    
    export.default = () => {
        return src('src/*.css')//创建文件读取流
            .pipe(cleanCss())//转换流
            .pipe(dest('dist'))//写入流:dest写入目标任务
    }
    

    案例1:基于Glup写一个自动化构建项目

    github.com/zce/zce-gul…

    文档框架:

    ├── public ······························ 不需要被加工,最终要拷贝到文件夹目录中的内容
    │   ├── favicon.ico ················· 站点图标
    ├── src ····························· 都会被构建,被转换
    │   ├── assets ················· 编写样式
    │   └── layouts ···················· 
    │   └── partials ···················· .html文件,通过模板编写
    ├── gulpfile.js ······················· 脚手架
    
    样式 / 脚本 / html编译任务

    ① 样式编译任务

    const sass = require('gulp-sass')
    
    const style = () => {
        return src('src/assets/styles/*.scss',{base:'src'})
        .pipe(sass())
        .pipe(dest('dist'))
    }
    
    module.exports = {
        style
    }
    

    ② 脚本编译任务

    const babel = require('gulp-babel')
    
    const script = () => {
        return src('src/assets/scripts/*.js', { base: 'src'})
        .pipe(babel({ presets: ['@babel/preset-env'] }))
        //babel是 的转换平台 / presets最新特性整体打包
        .pipe(dest('dist'))
    }
    
    //导出
    module.exports = {
        script
    }
    

    ③ html编译任务

    const data = {
        menus:[...],
    	pkg:require('./package.json')
        date:new Date()
    }
    
    const page = () => {
        return src('src/*.html', { base: 'src'})
        .pipe(swig({ data }))//模板引擎的插件转换
        .pipe(dest('dist'))
    }
    

    ④ 三个任务同时执行

    const { parallel } = require('gulp')
    
    const compile = parallel(style, script, page)
    
    module.exports = {
        //只导出compile任务
        compile
    }
    
    字体 / 图片文件转换
    const image = () => {
        return src('src/assets/images/**' , { base:'src' })
        .pipe(imagemin())
        .pipe(dest('dist'))
    }
    
    const font = () => {
        return src('src/assets/fonts/**' , { base:'src' })
        .pipe(imagemin())
        .pipe(dest('dist'))
    }
    
    module.exports = {
        image,
        font
    }
    
    其它文件 / 文件清除
    const extra = () => {
        return src('public/**', { base:'public' })
        .pipe(dest('dist'))
    }
    //避免产生混淆
    const build = parallel(compile, extra)
    
    module.exports = {
        build
    }
    
    const {series} = require('gulp')//需要先删除dist文件,再去生成dist
    
    const del = require('del')
    
    const clean = () => {
    	return del(['dist'])//放文件路径
    }
    
    const build = series(clean, parallel(compile, extra))
    
    module.exports = {
        build
    }
    
    自动加载插件

    使用load-plugins加载插件

    const loadPlugins = require('glup-load-plugins')
    
    const plugins = loadPlugins
    
    开发服务器 / 监视变化
    yarn add browser-sync --dev
    
    const { watch } = require('gulp')
    
    const browserSync = require('browser-sync')
    const bs = browserSync.create()//自动创建一个开发服务器,单独定义到一个任务启动
    
    const serve = () => {
      watch(['src/assets/images/**',
        'src/assets/fonts/**',
        'public/**'
      ], bs.reload) //bs.reload是一个任务
      watch('src/assets/scripts/*.js', script)
      watch('src/*.html', page)
      watch('src/assets/styles/*.scss', style) //监视样式文件、脚本等
      bs.init({
        notify: false, //提示
        port: 2080,
        // files: 'dist/**', //自动更新浏览器
        server: {
          baseDir: ['temp', 'src', 'public'],
          routes: {
            '/node_modules': 'node_modules'
          }
        }
      })
    }
    
    module.exports = {
        serve
    }
    
    构建优化

    ※ 如果页面不刷新,是因为swig模板引擎缓存的机制导致页面不会变化,此时需要额外将swig选项中的cashe设置为false

    const page = () => {
        ...
        .pipe(plugins.swig({ data, defaults: { cache: false } })) // 防止模板缓存导致页面不能及时更新
    }
    
    useref文件引用处理

    合并构建注释内容到一个js文件中

    yarn add gulp-useref --dev
    
    /** useref插件处理html内的构建注释,线上无法显示
     */
    const useref = () => {
        return src('dist/*.html', {base:'dist'})
        .pipe(plugins.useref({ searchPath: ['dist', '.']}))
        .pipe(dest('dist'))
    }
    

    构建注释全部去掉,把构建注释里的内容包含到一个文件中

    在useref中进行文件压缩(html / js / css)
    yarn add gulp-htmlmin gulp-uglify gulp-clean-css --dev
    

    需要判断什么文件

    yarn add gulp-if --dev
    
    重新规划构建过程

    build:上线之前执行的任务,最终构建的文件放到dist中

    develop:开发过程执行的任务,临时文件放在temp中

    //package.json
    "scripts": {
        "clean": "gulp clean",
        "build": "gulp build",
        "develop": "gulp develop",
      },
    
    //.gitignore
    
    /temp
    /dist
    

    案例2:封装工作流 / 构建多个项目

    即,提取一个可复用的工作流

    • glupfile + gulp = 构建工作流

    • gulpfile + gulp CLI = my-pages


    本文首发于我的GitHub博客,其它博客同步更新。


    起源地下载网 » 开发脚手架及封装自动化构建工作流

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元