我是靠谱客的博主 彪壮毛豆,最近开发中收集的这篇文章主要介绍前端工程化:脚手架工具和自动化构建前端工程化脚手架yarnYeomanPlop脚手架工作原理自动化构建,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章内容输出来源:拉勾教育大前端高薪训练营

前端工程化

前端工程化主要解决的问题

  • 传统语言或语法的弊端
  • 无法使用模块化/组件化
  • 重复的机械式工作
  • 代码风格统一、质量保证
  • 依赖后端服务接口支持
  • 整体依赖后端项目
  • 工程化表现:一切以提高效率、降低成本、质量保证为目的的手段都属于【工程化】

脚手架

  • 创建项目基础结构、提供项目规范和约定
  • 相同的组织结构、开发范式、模块依赖、工具配置、基础代码

yarn

  • 安装npm install yarn -g
  • 设置淘宝镜像yarn config set registry https://registry.npm.taobao.org/
  • yarn global bin 的路径添加到系统环境变量中

Yeoman

  • 脚手架的运行平台
//安装
npm install yo -g # or yarn global add yo
npm install generator-node -g # or yarn global add generator-node
//初始化项目
yo --no-deprecation

Sub Generator

只需要安装部分特定的文件,不需要初始化整个项目。例如:README.md

yo node:cli
yarn link
yarn

创建Generator模块

  • 必须 generator-[name]的文件夹
//generator-sample
yarn init
yarn add yeoman-generator

在generator-sample下创建app/index.js

// 此文件作为 Generator 的核心入口
// 需要导出一个继承自 Yeoman Generator 的类型
// Yeoman Generator 在工作时会自动调用我们在此类型中定义的一些生命周期方法
// 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入
const Generator = require('yeoman-generator')
module.exports= class extends Generator{
    writing(){
     // Yeoman 自动在生成文件阶段调用此方法
     // 我们这里尝试往项目目录中写入文件
        this.fs.write(
        	//路径:获取项目目录的绝对路径
            this.destinationPath('temp.txt'),
            //内容
            Math.random().toString()
        )
    }
}
yarn link
cd ..
mkdir my-proj
cd my-proj
yo sample
//创建成功

模板文件

  • 多个文件统一模板时使用
    在generator-sample下创建app/templates/foo.txt
//foo.txt
这是一个模板文件
内部可以使用 EJS 模板标记输出数据
例如:<%= title %>

其他的 EJS 语法也支持

<% if (success) { %>
hhh
<% }%>
//app/index.js
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
    writing() {
        // 模板文件路径
        const tmpl = this.templatePath('foo.txt')
        // 输出目标路径
        const output = this.destinationPath('foo.txt')
        // 模板数据上下文
        const context = { title: 'Hello ', success: false }

        this.fs.copyTpl(tmpl, output, context)
    }
}
yo sample

接收用户数据

//app/index.js
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
	prompting () {
		// Yeoman 在询问用户环节会自动调用此方法
		// 在此方法中可以调用父类的 prompt() 方法发出对用户的命令行询问
		return this.prompt([
			{
				type: 'input',//用户输入的方式去提交信息
				name: 'name', //得到结果的键
				message: ''//命令行工具中给用户的提示
				default:this.appname //默认值:this.appname为项目目录名称
			}
		])
		.then(answers =>{
			this.answers = answers
		})
	}
    writing() {
        // 模板文件路径
        const tmpl = this.templatePath('bar.html')
        // 输出目标路径
        const output = this.destinationPath('bar.html')
        // 模板数据上下文
        const context = this.answers

        this.fs.copyTpl(tmpl, output, context)
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
   <h1><%= name %></h1>
</body>
</html>

案例总结

  • 新建项目文件 generator-[name]
  • 初始化 yarn init
  • 安装依赖yarn add yeoman-generator
  • 新建generator/app/index.js
  • generator/app/templates 下放入所有模板文件
  • yarn link作为全局模块
  • 在需要生成项目文件夹中yo [name]
//index.js
const Generator = require('yeoman-generator')
module.exports = class extends Generator{
    prompting(){
        return this.prompt([
            {
                type:'input',
                name:'name',
                message:'Your project name',
                default:this.appname
            },
            {
                type:'input',
                name:'version',
                message:'Your project version',
                default:'1.0.0'
            }
        ])
        .then(answers =>{
            this.answers =answers
        })
    }
    
    writing(){
    	//所有模板文件的相对路径
        const templates = [
            'package.json',
            'README.md'
        ]

        templates.forEach( item =>{
            this.fs.copyTpl(this.templatePath(item),this.destinationPath(item),this.answers)
        })
    }
}

发布

  • yarn publish

Plop

  • 将 plop 模块作为项目开发依赖安装yarn add plop --dev
  • 在项目根目录下创建一个 plopfile.js
  • 在 plopfile.js 文件中定义脚手架任务
  • 编写用于生成特定类型文件的模板,遵循Handlebars语法
  • 通过 Plop 提供的 CLI 运行脚手架任务yarn plop [生成器名字]
// plopfile.js 
module.exports = plop => {
	//componet是生成器的名字
	plop.setGenerator('componet',{
		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'
			}
		]
		
	})
}

脚手架工作原理

  • 新建项目文件 sample-scaffolding
  • yarn init 初始化
  • 在package.json中,添加入口文件"bin":"cli.js"
  • 根目录下新建 cli.js 文件
  • yarn link 添加到全局
  • sample-scaffolding 执行
#!/usr/bin/env node

// Node CLI 应用入口文件必须要有这样的文件头
// 如果是 Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
// 具体就是通过 chmod 755 cli.js 实现修改

// 脚手架的工作过程:
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件

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(anwsers => {
  // console.log(anwsers)
  // 根据用户回答的结果生成文件

  // 模板目录
  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), anwsers, (err, result) => {
        if (err) throw err

        // 将结果写入目标文件路径
        fs.writeFileSync(path.join(destDir, file), result)
      })
    })
  })
})

自动化构建

  • 将开发环节的源代码 自动化构建 成浏览器中可以运行的代码

NPM Scripts

  • 在 package.json 中 添加 script 命令
  • npm 脚本有pre和post两个钩子,执行顺序npm run prebuild && npm run build && npm run postbuild
// package.json
"scripts": {
    "build": "sass scss/main.scss css/style.css",//sass编译器
    "preserve":"yarn build",
    "serve":"browser-sync ."//测试服务器
  },

browser-sync

  • borwser-sync . --files "*css/*.css" 监听css文件

sass

  • "build": "sass scss/main.scss css/style.css --watch"监听sass文件,实时修改,但会阻塞命令,所以需要npm-run-all模块

npm-run-all

  • run-p build serve 同时执行build和serve

Grunt

yarn init --yes
yarn add grunt
code gruntfile.js//添加入口文件
yarn grunt foo//foo代表名字
// 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 执行时可以省略
  // 第二个参数可以指定此任务的映射任务,
  // 这样执行 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() 方法创建回调函数
  // 由于函数体中需要使用 this,所以这里不能使用箭头函数
  grunt.registerTask('async-task', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('async task working~')
      done()
    }, 1000)
  })
}


Grunt标记失败

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

Grunt配置方法

  • initConfig中属性名字要等于任务名字
//gruntfile.js
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: {
      options: {
        msg: 'task options'
      },
      foo: {
        options: {
          msg: 'foo target options'
        }
      },
      bar: '456'
    }
  })

  grunt.registerMultiTask('build', function () {
  //this.target=属性名,this.data=值
    console.log(this.options())//this.options=initConifg里的所有optinons
    
  })
}

Grunt插件

  • 清除插件
  • yarn add grunt-contrib-clean//安装
  • yarn grunt clean//执行
module.exports = grunt => {
  grunt.initConfig({
    clean: {
      temp: 'temp/**'
      //清除temp文件下所有
    }
  })
  
  grunt.loadNpmTasks('grunt-contrib-clean')
}
  • sass插件 sass转化
  • yarn add grunt-sass sass --dev
  • babel es转换
  • yarn add grunt-babel @babel/core @babel/preset-env --dev
  • watch 监控页面变化
  • yarn add grunt-contrib-watch --dev
  • 一次性加载插件
  • yarn add load-grunt-tasks --dev
const sass = require('sass')
const loadGruntTasks = require('load-grunt-tasks')

module.exports = grunt => {
  grunt.initConfig({
    sass: {
      options: {
      //是否生成map文件
        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'],//监听
        /*回调,babel任务*/
        tasks: ['babel']
      },
      css: {
        files: ['src/scss/*.scss'],
        tasks: ['sass']
      }
    }
  })

 /*grunt.loadNpmTasks('grunt-sass')
   自动加载所有的 grunt 插件中的任务*/
   loadGruntTasks(grunt)
/* 应该先执行一次*/
  grunt.registerTask('default', ['sass', 'babel', 'watch'])
}

Gulp

  • 高效、易用
  • yarn add gulp --dev
  • yarn gulp <任务名>
//gulpfile.js
// gulp 的任务函数都是异步的
// 可以通过调用回调函数标识任务完成
exports.foo = done => {
  console.log('foo task working~')
  done() // 标识任务执行完成
}

// default 是默认任务
// 在运行是可以省略任务名参数
exports.default = done => {
  console.log('default task working~')
  done()
}

// v4.0 之前需要通过 gulp.task() 方法注册任务
const gulp = require('gulp')

gulp.task('bar', done => {
  console.log('bar task working~')
  done()
})

Gulp多任务

//gulpfile.js
const { series, parallel } = require('gulp')
// 让多个任务按照顺序依次执行
exports.foo = series(task1, task2, task3)

// 让多个任务同时执行
exports.bar = parallel(task1, task2, task3)

Gulp异步

  • done回调
  • promise和async
  • stream
const fs = require('fs')

exports.callback = done => {
  console.log('callback task')
  done()
}

exports.callback_error = done => {
  console.log('callback task')
  done(new Error('task failed'))
}

exports.promise = () => {
  console.log('promise task')
  return Promise.resolve()
}

exports.promise_error = () => {
  console.log('promise task')
  return Promise.reject(new Error('task failed'))
}

const timeout = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  })
}

exports.async = async () => {
  await timeout(1000)
  console.log('async task')
}

exports.stream = () => {
  //读取文件流
  const readStream = fs.createReadStream('yarn.lock')
  //写入文件流
  const writeStream = fs.createWriteStream('a.txt')
  //复制流
  readStream.pipe(writeStream)
  return readStream
  createWriteStream
}

// exports.stream = done => {
//   const readStream = fs.createReadStream('yarn.lock')
//   const writeStream = fs.createWriteStream('a.txt')
//   readStream .pipe(writeStream )
//   readStream .on('end', () => {
//     done()
//   })
// }

Glup构建过程

  • 读取文件——转换文件——写入文件
//gulpfile.js
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插件

  • gulp-sass
    • 安装yarn add gulp-sass --dev
    • 使用.pipe(sass( { outputStyle: 'compact'}))
    • 默认 "_"开头的文件不编译
    • 作用:编译sass
  • gulp-babel
    • 安装yarn add gulp-babel --dev
    • yarn add @babel/core @babel/preset-env --dev
    • 使用.pipe(babel({presets:['@babel/preset-env']}))
    • 编译es6
  • gulp-swig
    • 安装yarn add gulp-swig --dev
    • .pipe(plugins.swig({ data, defaults: { cache: false } }))
    • 编译模板文件
  • gulp-imagemin
    • 安装yarn add gulp-imagemin --dev
    • .pipe(plugins.imagemin())
    • 压缩图片和字体文件
  • del
    • yarn add del --dev
    • 删除文件
  • gulp-load-plugins
    • 安装yarn add gulp-load-plugins --dev
    • const loadPlugins = require('gulp-load-plugins'); const plugins = loadPlugins()
    • 统一引入插件
  • browser-sync
    • 安装yarn add browser-sync --dev
    • const loadPlugins = require('gulp-load-plugins'); const plugins = loadPlugins()
    • 开发服务器热更新
  • gulp-useref
    • 安装yarn add gulp-useref --dev
    • 将构建注释内的引入资源合并

Gulp案例

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

const del = require('del')
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' }))
    .pipe(dest('temp'))
    //执行style时,刷新bs服务器。可以去掉bs.init中的files属性
    .pipe(bs.reload({ stream: true }))
}

const script = () => {
  return src('src/assets/scripts/*.js', { base: 'src' })
    .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 = () => {
	//监听scss改变时,执行style任务
  watch('src/assets/styles/*.scss', style)
  watch('src/assets/scripts/*.js', script)
  watch('src/*.html', page)
  /*images和fonts等文件只是压缩,没不要在开发阶段实时构建。直接刷新服务器即可*/
  // 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,
    //监听的目录。因为temp是构建后的目录,所以上述任务执行时会更新temp。但是上述任务已经添加了.pipe(bs.reload({ stream: true }))去回调更新,所以不需要这个属性了
    // files: 'temp/**',
    server: {
    // 服务器目录,包括转换后的temp,存放image的src等
      baseDir: ['temp', 'src', 'public'],
      routes: {
        '/node_modules': 'node_modules'
      }
    }
  })
}

const useref = () => {
//将temp下的html文件进行修改
  return src('temp/*.html', { base: 'temp' })
  	// 查找的文件目录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
}

封装工作流

上一篇:ES 新特性 与 TypeScript、JS性能优化

最后

以上就是彪壮毛豆为你收集整理的前端工程化:脚手架工具和自动化构建前端工程化脚手架yarnYeomanPlop脚手架工作原理自动化构建的全部内容,希望文章能够帮你解决前端工程化:脚手架工具和自动化构建前端工程化脚手架yarnYeomanPlop脚手架工作原理自动化构建所遇到的程序开发问题。

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

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

评论列表共有 0 条评论

立即
投稿
返回
顶部