脚手架工具


本质作用

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

意义

  • 减少重复性的工作,不需要复制其他项目再删除无关代码,或者从零创建一个项目和文件
  • 可以根据交互动态生成项目结构和配置文件
  • 多人协作更为方便,不需要把文件传来传去

常用脚手架工具

脚手架

作用

create-react-app

创建 React 项目

vue-cli

创建 Vue 项目

angular-cli

创建 Angular 项目

yeoman

通用性脚手架工具

依照模板生成特定的项目结构

plop

创建特定类型的脚手架

自定义脚手架

1. 安装 基本依赖

// 引入 inquirer 模块
yarn add inquirer
// 安装模板引擎
yarn add ejs

2. 配置 package.json 文件

// package.json
{
  "name": "sample",
  "version": "1.0.0",
  "main": "index.js",
  "bin":"cli.js",   //脚手架入口文件
  "license": "MIT"
}

3. 添加入口文件

// cli.js
#!/usr/bin/env node
const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer')
const ejs = require('ejs')
// 发起询问
inquirer.prompt([
    {
        type: 'input',
        name: 'name', // 对应参数
        message: 'Project name?' // 问题内容
    }
]).then(answers => {
    // 模板目录
    const tmplDir = path.join(__dirname, 'templates')
    // 目标目录
    const destDir = process.cwd()
    // 将模板下的文件全部转换到目标目录
    fs.readdir(tmplDir, (err, files) => {
        if (err) throw err
        files.forEach(file => {
            // 通过模板引擎渲染文件
            // (文件的绝对路径,模板引擎数据上下文,回调函数)
            ejs.renderFile(path.join(tmplDir, file), answers, (err, result) => {
                if (err) throw err
                // 将结果写入目标目录
                fs.writeFileSync(path.join(destDir, file), result)
            })
        })
    })
})

4. 创建模板文件夹( templates )并在路径下添加模板文件

Yeoman


安装

yarn global add yo
yarn global add generator-node

使用

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

创建 Generator 模块

1. 创建模块目录

2. 初始化 package.json

3. 安装 yeoman 生成器基类

yarn add yeoman-generator

4. 创建 Generator 基本结构

|- generators/ ....................... 生成器目录
|  |- app/ ........................... 默认生成器目录
|  |  |- index.js .................... 默认生成器实现
|  |
|  |- component/ ..................... 其他生成器目录
|     |- index.js .................... 其他生成器实现
|
|- package.json ...................... 模块包配置文件

5. 编写模块指令

// generators/app/index.js
const Generator = require('yeoman-generator')
module.exports = class extends Generator{
    initianlizing(){
        //获取当前项目状态,获取基本配置参数等
    }
    prompting(){
        //向用户展示交互式问题收集关键参数
    }
    configuring(){
        //保存配置相关信息且生成配置文件(名称多为'.'开头的配置文件,例如.editorconfig)
    }
    default(){
        //未匹配任何生命周期方法的非私有方法均在此环节*自动*执行
    }
    writing(){
        //依据模板进行新项目结构的写操作
    }
    conflicts(){
        //处理冲突(内部调用,一般不用处理)
    }
    install(){
        //使用指定的包管理工具进行依赖安装(支持npm,bower,yarn)
    }
    end(){
        //结束动作,例如清屏,输出结束信息,say GoodBye等等
    }
}

6. 绑定至全局

yarn link

7. 运行

yo sample

Gulp


Grunt 插件库

核心工作原理

const fs = require('fs')
const { Transform } = require('stream')
exports.default = () => {
    // 文件读取流
    const read = fs.createReadStream('main.css')
    // 文件写入流
    const write = fs.createWriteStream('main.min.css')
    // 文件转换器
    const transform = new Transform({
        transform(chunk, encoding, callback) {
            // 核心转换过程实现
            // chunk => 读取流中读取到的内容(Buffer)
            const input = chunk.toString()
            // 去除空格和注释
            const output = input.replace(/\/\*.+?\*\//g, '').replace(/\s+/g, '')
            callback(null, output)
        }
    })
    // 把读取出来的文件流导入文件流
    read.pipe(transform) // 转换
        .pipe(write) // 写入

    return read
}

安装

yarn add gulp --dev

创建入口文件

// gulpfile.js
exports.foo = done => {
    console.log('foo task working~')
    done() // 表示任务完成
}
exports.default = done => {
    console.log('default task working~')
    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.foo = parallel(task1, task2, task3) // 串行

异步任务

// 回调函数方案
exports.callback = done => {
    console.log('callback task~')
    done()
}
exports.callback_error = done => {
    console.log('callback task~')
    done(new Error('task failed!'))
}
// Promise方案
exports.promise = done => {
    console.log('promise task~')
    return Promise.resolve() // 无需传值,gulp 会自动忽略掉值
}
exports.callback = done => {
    console.log('callback task~')
    return Promise.reject(new Error('promise task failed!'))
}
// async + await 方案
const timeout = time => {
    return new Promise((resolve => {
        setTimeout(resolve, time)
    }))
}
exports.async = async () => {
    await timeout(1000)
    console.log('async task~')
}
// stream 方案
exports.stream = () => {
    const readStream = fs.createReadStream('package.json')
    const writeStream = fs.createWriteStream('temp.text')
    readStream.pipe(writeStream)
    return readStream
}

 案例

const { src, dest, parallel, series, watch } = require('gulp')

const del = require('del') // yarn add del --dev
const browserSync = require('browser-sync')

const loadPlugins = require('gulp-load-plugins')
//自动加载插件
const plugins = loadPlugins()
// 创建一个开发服务器
const bs = browserSync.create()

const 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()
}
// 清除导出路径下已有的文件
const clean = () => {
    return del(['dist', 'temp'])
}
// 样式编译
const style = () => {
    return src('src/assets/styles/*.scss', { base: 'src' })
    .pipe(plugins.sass({ outputStyle: 'expanded' })) // yarn add gulp-sass --dev
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true })) // 更新时将文件内容以流的方式推入浏览器
}
// 脚本编译
const script = () => {
    return src('src/assets/scripts/*.js', { base: 'src' }) // 配置 base,已保留原有的目录结构
    .pipe(plugins.babel({ presets: ['@babel/preset-env'] }))
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}
// 页面模板编译
const page = () => {
    return src('src/*.html', { base: 'src' })
    .pipe(plugins.swig({ data, defaults: { cache: false } })) // 防止模板缓存导致页面不能及时更新
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }))
}
// 图片和文字文件转换
const image = () => {
    return src('src/assets/images/**', { base: 'src' })
    .pipe(plugins.imagemin()) // 图片压缩
    .pipe(dest('dist'))
}
const font = () => {
    return src('src/assets/fonts/**', { base: 'src' })
    .pipe(plugins.imagemin()) // 不可压缩的文件则不会被压缩
    .pipe(dest('dist'))
}
// 其他文件
const extra = () => {
    return src('public/**', { base: 'public' })
    .pipe(dest('dist'))
}
// 开发服务器
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)
    watch([
        'src/assets/images/**',
        'src/assets/fonts/**',
        'public/**'
    ], bs.reload) // 文件发生变化时,重新请求文件

    bs.init({
        notify: false,
        port: 2080,
        // open: false,
        // files: 'dist/**',
        server: {
            baseDir: ['temp', 'src', 'public'],
            routes: {
                '/node_modules': 'node_modules'
            }
        }
    })
}
// 文件引用处理
// 将文件引用整合到一个文件中去
const useref = () => {
    return src('temp/*.html', { base: 'temp' })
    .pipe(plugins.useref({ searchPath: ['temp', '.'] }))
    // html js css
    .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('dist'))
}
// 组合用于编译、压缩、转换的任务
const compile = parallel(style, script, page)
// 上线之前执行的任务
const build = series(
    clean,
    parallel(
        series(compile, useref),
        image,
        font,
        extra
    )
)

const develop = series(compile, serve)

// 导出
module.exports = {
    clean,
    build,
    develop
}

Grunt


安装

yarn add grunt

配置入口文件

// gruntfile.js
module.exports = grunt => {
    // 配置
    grunt.initConfig({
        build: {
            // options 作为任务的配置选项
            options: {
                foo: 'test'
            },
            // 其余的均作为多任务目标(子任务)
            css: '1',
            js: '2'
        },
        // 插件使用
        clean: {
            temp: 'temp/' // 执行目标路径
        }
    })
    // 加载插件
    grunt.loadNpmTasks('grunt-contrib-clean')
    // 创建任务
    grunt.registerTask('bad', () => {
        console.log('bad workding~')
        return false
    })
    grunt.registerTask('foo', () => {
        console.log('foo workding~')
        return false
    })
    // 串联已创建的任务
    grunt.registerTask('default', ['foo', 'bad', 'bar'])
    // 异步任务
    grunt.registerTask('bad-async', function() {
        const done = this.async()
        setTimeout(() => {
            console.log('bad async')
            done(false)
        }, 1000)
    })
    // 多目标模式,可以让任务根据配置形成多个子任务
    grunt.registerMultiTask('build', function() {
        console.log('build task')
        console.log(this.options()) // 获取任务配置选项
        console.log(`target: ${this.target}, data: ${this.data}`)
    })
}

插件引入

yarn grunt "ModuleName"

常用插件配置

// gruntfile.js
const sass = require('sass')
const loadGruntTasks = require('load-grunt-tasks')
module.exports = grunt => {
    grunt.initConfig({
        // yarn grunt sass
        sass: {
            options: {
                soruceMap: true, // 是否生成 sourceMap
                implementation: sass
            },
            main: {
                files: {
                    'dist/css/main.css': 'src/scss/main.scss'
                }
            }
        },
        // yarn add load-grunt-tasks --dev
        babel: {
            options: {
                presets: ['@babel/preset-env']
            },
            main: {
                files: {
                    'dist/js/app.js': 'src/js/app.js'
                }
            }
        }
        // yarn grunt-contrib-watch --dev
        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'])
}

Plop


安装

yarn add plop --dev

配置入口文件

//plopfile.js
// plop入口文件 需要导出一个函数
module.exports=function(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:"templates/template.hbs"//模板所在地址使用hbs文件
            },{
                type:"add",
                path:"src/components/{{name}}/{{name}}.css",
                templateFile:"templates/template.hbs"
            },{
                type:"add",
                path:"src/components/{{name}}/{{name}}.html",
                templateFile:"templates/template.hbs"
            }
        ]
    })
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐