前端工程化

388 阅读8分钟

前端工程化可以解决的问题:

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

工程化的表现:

  • 一切重复的工作都应该被自动化,但工程化不等于某个工具;
  • 成熟的工程化集成:creact-react-app vue-cli angular-cli gatsby-cli

脚手架工具:

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

脚手架工具Yeoman:

基本使用:

  • 安装:全局安装Yeoman
yarn global add yo
  • 安装对应Generator:下面安装的是node模块
yarn global add generator-node
  • 运行:在项目文件夹下运行
yo node
  • 常规使用步骤:
    • 明确需求
    • 找到合适的Generator
    • 全局范围安装找到的Generator
    • 通过yo运行对应的Generator
    • 通过命令行交互填写选项
    • 生成你所需要的项目结构

自定义Generator:基于Yeoman搭建自己的脚手架

  • 创建Generator模块:
  • 基本结构:
  • 步骤:
mkdir generatgor-sample //生成文件夹作为目录,规则 => 模块名称必须为generatgor-<name>
yarn init 
yarn add yeoman-generator //提供生成器基类
  • 创建一下目录结构:index.js是Generator的核心入口:

  • 在index.js文件中:需要导出一个继承自 Yeoman Generator 的类型,Yeoman Generator 在工作时会自动调用我们在此类型中定义的一些生命周期方法,我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入:
const Generator = require('yeoman-generator')//导入生成器

module.exports = class extends Generator {
    //命令行交互询问
  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
    })
  }
    
   //文件写入 
  writing() {
    // Yeoman 自动在生成文件阶段调用此方法

    // 我们这里尝试往项目目录中写入文件:创建temp.txt文件并写入一串随机数
     //与node不同这里的fs更为强大 
    // this.fs.write(
    //   this.destinationPath('temp.txt'),
    //   Math.random().toString()
    // )
    // -------------------------------------------------------

    // 通过模板方式写入文件到目标目录:先创建模板

    // // 模板文件路径
    // const tmpl = this.templatePath('foo.txt')
    // // 输出目标路径
    // const output = this.destinationPath('foo.txt')
    // // 模板数据上下文
    // const context = { title: 'Hello~ 德善啊', success: false }
    // // 自动映射模板文件到生成的文件
    // this.fs.copyTpl(tmpl, output, context)

    // -------------------------------------------------------

    // 模板文件路径
    const tmpl = this.templatePath('bar.html')
    // 输出目标路径
    const output = this.destinationPath('bar.html')
    // 根据用户输入形成模板数据上下文
    const context = this.answers

    this.fs.copyTpl(tmpl, output, context)
  }
}
yarn link  //使之成为全局模块包
yo sample //运行

Plop:一个小而美的脚手架工具(创建特定同类型文件小工具)

yarn add plop -dev //安装

根目录下新建plopfile.js:入口文件

模板文件创建

// 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'
      }
    ]
  })
}

常用的自动化构建工具:Gulp Grunt FIS

  • Grunt:构建速度较慢 => 不适用于大型项目
  • Gulp:前端比较流行的构建工具,高效,易用
  • FIS:资源加载,大而全

Grunt使用:

yarn add grount

创建配置文件grountfile.js: Grunt 的入口文件,用于定义一些需要 Grunt 自动执行的任务, 需要导出一个函数,此函数接收一个 grunt 的对象类型的形参,grunt 对象中提供一些创建任务时会用到的 API

const sass = require('sass')
const loadGruntTasks = require('load-grunt-tasks')

module.exports = grunt => {
  grunt.registerTask('foo', 'a sample task', () => {
    console.log('hello grunt')
  })

  grunt.registerTask('bar', () => {
    console.log('other task')
  })

   // 1、默认任务
  // 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~')
  })

  //2、异步任务
  // 默认 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)
  })
    
  //3、失败任务
  // 任务函数执行过程中如果返回 false
  // 则意味着此任务执行失败
  grunt.registerTask('bad', () => {
    console.log('bad working~')
    return false
  })
  // 如果一个任务列表中的某个任务执行失败
  // 则后续任务默认不会运行
  // 除非 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)
  })
    
  //4、多目标任务
  // 多目标模式,可以让任务根据配置形成多个子任务
  grunt.initConfig({
    build: {
      options: {
        msg: 'task options'
      },
      foo: {
        options: {
          msg: 'foo target options'
        }
      },
      bar: '456'
    }
  })

  grunt.registerMultiTask('build', function () {
    console.log(this.options())
  })
    
  //5、grount插件使用
   grunt.initConfig({
    clean: {
      temp: 'temp/**'
    }
  })
  grunt.loadNpmTasks('grunt-contrib-clean')
  //其他插件
  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'])
}
yarn grount foo //执行任务
yarn grount     //执行默认任务
yarn grount --help  //查看任务自定义描述
yarn grount default --force  //强制执行多有参数,即使任务中有失败,后续任务也会执行

yarn add grount-contrib-clean  //安装自动清除项目临时文件
yarn grount clean  //运行

Gulp使用:

yarn add gulp --dev

创建配置文件gulpfile.js: 导出的函数都会作为 gulp 任务,gulp 的任务函数都是异步的

// 1、基本使用
// 导出的函数都会作为 gulp 任务
// exports.foo = () => {
//   console.log('foo task working~')
// }

// 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()
})

//2、组合任务
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)


//3、异步任务
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'))
}
//异步方式1:promise
exports.promise = () => {
  console.log('promise task')
  return Promise.resolve()
}
//promise失败
exports.promise_error = () => {
  console.log('promise task')
  return Promise.reject(new Error('task failed'))
}
//异步方式2:promise语法糖 async await
const timeout = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  })
}
exports.async = async () => {
  await timeout(1000)
  console.log('async task')
}
//异步方式3:stream
exports.stream = () => {
  const read = fs.createReadStream('yarn.lock')
  const write = fs.createWriteStream('a.txt')
  read.pipe(write)
  return read
}

Gulp构建过程的核心原理:读取流 转换流 写入流

Gulp自动化网页构建流:

  • gulpfile.js配置文件内容:
// src读取流、dest写入流、同步组合任务、异步zuherenwu
const { src, dest, parallel, series, watch } = require('gulp')
// 清除方法
const del = require('del')
// 启动开发服务器
const browserSync = require('browser-sync')
const bs = browserSync.create()
// 自动加载插件
const loadPlugins = require('gulp-load-plugins')
const plugins = loadPlugins()
// 返回 Node.js 进程的当前工作目录
const cwd = process.cwd()

// default config与数据
let config = {};
try {
  const loadConfig = require(`${cwd}/pages.config.js`)
  config = Object.assign({}, config, loadConfig)
} catch (e) {}

// 文件清除
const clean = () => {
  return del([config.build.dist, config.build.temp])
}

// 样式文件编译
const style = () => {
  return src(config.build.paths.styles, { base: config.build.src, cwd: config.build.src })
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest(config.build.temp))
    .pipe(bs.reload({ stream: true }))
}

// js文件编译
const script = () => {
  return src(config.build.paths.scripts, { base: config.build.src, cwd: config.build.src })
    .pipe(plugins.babel({ presets: [require('@babel/preset-env')] }))
    .pipe(dest(config.build.temp))
    .pipe(bs.reload({ stream: true }))
}

// 页面编译
const page = () => {
  return src(config.build.paths.pages, { base: config.build.src, cwd: config.build.src })
    .pipe(plugins.swig({ data: config.data, defaults: { cache: false } }))
    .pipe(dest(config.build.temp))
    .pipe(bs.reload({ stream: true }))
}

// 图片编译
const image = () => {
  return src(config.build.paths.images, { base: config.build.src, cwd: config.build.src })
    .pipe(plugins.imagemin())
    .pipe(dest(config.build.dist))
}

// 字体编译
const font = () => {
  return src(config.build.paths.fonts, { base: config.build.src, cwd: config.build.src })
    .pipe(plugins.imagemin())
    .pipe(dest(config.build.dist))
}

// 其他文件编译
const extra = () => {
  return src('**', { base: config.build.public, cwd: config.build.public })
    .pipe(dest(config.build.dist))
}

// 热更新开发服务器
const serve = () => {
  // 监听样式、js、页面更新并编译
  watch(config.build.paths.styles, { cwd: config.build.src }, style)
  watch(config.build.paths.scripts, { cwd: config.build.src }, script)
  watch(config.build.paths.pages, { cwd: config.build.src }, page)

  // 监听其他文件更新即可
  watch([
    config.build.paths.images,
    config.build.paths.fonts
  ], { cwd: config.build.src }, bs.reload)

  watch('**', { cwd: config.build.public }, bs.reload)

  // 开发服务器配置
  bs.init({
    notify: false,
    port: 2080,
    server: {
      baseDir: [config.build.temp, config.build.dist, config.build.public],
      routes: {
        '/node_modules': 'node_modules'
      }
    }
  })
}

// 文件引用处理
const useref = () => {
  return src(config.build.paths.pages, { base: config.build.temp, cwd: config.build.temp })
    .pipe(plugins.useref({ searchPath: [config.build.temp, '.'] }))
    // html js css压缩
    .pipe(plugins.if(/\.js$/, plugins.uglify()))
    .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
    .pipe(plugins.if(/\.html$/, plugins.htmlmin({
      // 页面中 空格 css js压缩
      collapseWhitespace: true,
      minifyCSS: true,
      minifyJS: true
    })))
    .pipe(dest(config.build.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
}
  • package.json需要安装的依赖以及配置运行设置
"scripts": {
    "clean": "gulp clean",
    "develop": "gulp develop",
    "lint": "gulp lint",
    "serve": "gulp serve",
    "build": "gulp build",
    "start": "gulp start",
    "deploy": "gulp deploy --production"
  },
"dependencies": {
    "bootstrap": "4.4.1",
    "browser-sync": "^2.26.13",
    "del": "^6.0.0",
    "gulp": "^4.0.2",
    "jquery": "3.4.1",
    "popper.js": "1.16.1"
  },
  "devDependencies": {
    "@babel/core": "^7.12.10",
    "@babel/preset-env": "^7.12.11",
    "gulp-babel": "^8.0.0",
    "gulp-clean": "^0.4.0",
    "gulp-clean-css": "^4.3.0",
    "gulp-htmlmin": "^5.0.1",
    "gulp-if": "^3.0.0",
    "gulp-imagemin": "^7.1.0",
    "gulp-load-plugins": "^2.0.6",
    "gulp-sass": "^4.1.0",
    "gulp-swig": "^0.9.1",
    "gulp-uglify": "^3.0.2",
    "gulp-useref": "^5.0.0"
  },

经过两个文件的配置与安装即可创建一个简单的自动化构建流。


结语:以上内容全学习时手敲记录,无复制粘贴,全原创,希望可以给各位小伙伴带来收获,如有错误的地方或有疑问欢迎留言,感谢阅读!


祝各位前端程序猿前程似锦,一路向北!