工程化、脚手架、自动化构建

85 阅读6分钟

简答题

1、谈谈你对工程化的初步认识,结合你之前遇到过的问题说出三个以上工程化能够解决问题或者带来的价值。

答:
1、工程化:使用统一、规范化的流程来管理整个项目的开发过程;提高效率,降低工作时间成本;
2、价值
(1)、减少重复性工作,减少复制粘贴工作:规范了项目的基本布局,不会因为每次开发新项目都要考虑是从哪一个项目copy脚手架过来使用
(2)、提高效率:不用像以往的手动用ps、或者压缩网站进行图片压缩;甚至于以往使用第三方代码压缩工具来监听项目文件,进行转译、压缩;(忘记是什么工具了,直接运行在后台)
(3)、项目组统一使用统一配置,在相互写作也不会因为彼此使用的版本不一致,导致项目合并之后无法运行,或者方法使用因版本高低有所差异

 

2、你认为脚手架除了为我们创建项目结构,还有什么更深的意义?

答:
脚手架是一个项目运行打了一个基底,每个项目都在整个基底上构建;就好比发每一栋楼因为同样的脚手架而具备一样的建筑风格,每一个项目也是如此;
相同的项目结构,更利于成员的相互协作,降低沟通成本。是约束,也是规范,跟有利于代码的维护跟团队合作。

 

 

编程题

1、概述脚手架实现的过程,并使用 NodeJS 完成一个自定义的小型脚手架工具

  • mkdir wly-vue 新建目录
  • yarn init 安装package.json { "name": "wly-vue", "version": "1.0.0", "main": "index.js", "license": "MIT" }
  • 在json中添加bin字段,用于指定cli应用的入口文件 { "name": "wly-vue", "version": "1.0.0", "main": "index.js", "license": "MIT", "bin": "cli.js" }
  • 在cli中第一行文件头,cli文件应用入口声明 #!/usr/bin/env node
  • yarn link // 将应用link到全局,以便在命令行中直接使用(命令就是就是之前yarn init的工程名)
  • wly-vue 运行
  • yarn add inquirer // 安装询问模块
  • yarn add ejs // 添加模板引擎
  • 在cli文件中,在inquirer.prompt回调结果获取answer,通过fs读取path引入的模板,再用fs写入到目标目录  
#!/usr/bin/env node

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

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

const inquirer = require('inquirer'); 
const ejs = require('ejs'); 
const path = require('path'); 
const fs = require('fs'); 

inquirer.prompt([
    {
        type: 'input', // 问题输入方式
        name: 'name',
        message: 'project name?'
    }
])
.then(answer => {
    console.log(answer);
    // 根据用户回答生成文件

    // 模板目录
    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), answer, (err, res) => {
                if (err) {
                    throw err;
                }
                fs.writeFile(path.join(destDir, file), res, (_err, _res) => {
                    if (_err) {
                        throw _err;
                    }
                    console.log(_res);
                });
            })
        });
    })
})

2、尝试使用 Gulp 完成项目的自动化构建 ( 先要作的事情 )

(html,css,等素材已经放到code/pages-boilerplate目录) 安装gulp,并新建gulpfile.js

yarn init --yes

yarn add gulp

code gulpfile.js
// 实现这个项目的构建任务
const { src, dest, parallel, series, watch } = require('gulp')

// const sass = require('gulp-sass');
// // @ sass转译
// // yarn add gulp-sass --dev
// const babel = require('gulp-babel'); 

// // @ es6最新特性转入 
// // yarn add gulp-babel --dev
// // yarn add @babel/core @babel/preset-env --dev
// const swig = require('gulp-swig');

// // @ 酱data数据导入html模板
// // yarn add gulp-swig --dev
// const imagemin = require('gulp-imagemin');

// // @ 图片压缩
// // yarn add gulp-imagemin --dev
// // !!! 注意: 由于npm的安装模式,可能会导致部分包安装确实,在运行imagesmin任务时候
// // 会报错
// // Couldn't load default plugin "gifsicle"
// // Couldn't load default plugin "optipng"
// // 故可通过cnpm的方式安装一次
// // cnpm install gulp-imagemin --save-dev

const del = require('del');
// @ 执行clean任务,del事件执行
// yarn add del --dev

const loadPlugins = require('gulp-load-plugins')
// @ 自动加载插件
// yarn add gulp-load-plugins --dev
const plugins = loadPlugins();

const browserSync = require('browser-sync')
// @ 安装热更新
// yarn add browser-sync --dev
const bs = browserSync.create()
// bs === browserServer
// 自动创建一个开发服务器

// @ useref,使用依赖,引用关系
// yarn add gulp-useref

// @ 压缩HTML、JS、CSS
// yarn add htmlmin gulp-uglify gulp-clean-css --dev
// yarn add gulp-if --dev

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 style1 = () => {
  return src('src/assets/styles/*.scss', { base: 'src' }) // base 基准路径
    .pipe(plugins.sass({ // 通过c++执行的模块
      // sass 对于下划线的文件,会认为是依赖引入,所以默认不转译
      outputStyle: 'expanded' // 完全展开编译
    }))
    .pipe(dest('dist'))
}

const script1 = () => {
  return src('src/assets/scripts/*.js', {base: 'src'})
  .pipe(plugins.babel({
     // bable() 不像sass那样会默认转译,需要手动选择模块
     // 只是提供一个平台
     // 真正转译的是内部的插件,比如preset-env
     // 有的项目也会直接维护一个babelrc的文件
     // 所以需要执行 yarn add @babel/core @babel/preset-env --dev
    presets: ['@babel/preset-env'] // preset-env模块,引入es新特性,进行转译
  }))
  .pipe(dest('dist'))
}

const page1 = () => {
  return src('src/*.html', {base: 'src'}) // src/**/*/.html 遍历全部文件夹中的html文件
  .pipe(plugins.swig({ data })) // 通过data,swig写入到html中的数据
  .pipe(dest('dist'))
}

const image1 = () => {
  return src('src/assets/images/**', { base: 'src' })
    .pipe(plugins.imagemin()) // 通过c++执行的模块
    // 删除图片原信息来压缩
    // svg进行代码压缩
    .pipe(dest('dist'))
}

const font1 = () => {
  return src('src/assets/fonts/**', { base: 'src' })
    .pipe(plugins.imagemin()) // 通过c++执行的模块
    // 删除图片原信息来压缩
    // svg进行代码压缩
    .pipe(dest('dist'))
}

const extra1 = () => {
    // 额外文件操作,直接copy的
    return src('public/**', {base: 'public'})
        .pipe(dest('dist'))
        .pipe(bs.reload({ stream: true }))
    // reload执行完之后,返回一个读写流,
    // 此处声明可以将执行完的流往浏览器推
    // 可以替代bs.init中的files属性
}

const compile1 = parallel(
  // 组合任务   parallel 同时执行
  style1,
  script1,
  page1,
  image1,
  font1,
)

const server = () => {
    watch('src/assets/styles/*.scss', style1);
    watch('src/assets/scripts/*.js', script1);
    watch('src/*html', page1);
    watch([
        'src/assets/images/**',
        'src/assets/fonts/**',
        'public/**'
    ], bs.reload);
    // 监听这些不需要执行任务的目录
    // 然后执行刷新页面reload
    bs.init({
        notify: false, // 不显示提示信息
        prot: 2080, // 默认启动端口
        open: true, // 自动打开
        files: 'dist/**', // 监听
        server: {
            baseDir: 'dist',  // 运行加工过后的web文件
            // ['dist', 'src', 'public']
            // 文件如果只是复制,或者文件的操作在开发阶段不需要构建(图片压缩、等—)
            // 那么可以不需要watch这个任务,可以直接对所在目录进行监听
            // 只编译需要在开发环境编译才能运行的文件
            routes: { // 先走routes的配置,再走baseDir下的文件
                '/node_modules': 'node_modules'
            }
        }
    })
}


/**
 *  <!-- build:css assets/styles/vendor.css -->
 * <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css">
 * <!-- endbuild -->
 * 这里的引入,之所以开发环境成功,是因为server任务中进行了routes路由置顶
 * 生产环境就需要用useref来进行打包成vendor.css
*/
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({
        collapseWithspace: true, // 去除空白
        minifyCss: true, // 压缩行内css
        minifyJs: true, // 压缩行内js
    })))
    .pipe(dest('release'))
}

const build1 = parallel(compile1, extra1);

const clean = () => {
  // del不属于gulp,他却是一个promise,可以在gulp中执行
  return del(['dist', 'temp'])
}

const build = series(clean, build1, useref);

module.exports = {
    build,
    clean,
    server,
}

文章内容输出来源:拉勾教育Java高薪训练营;